1.进程
进行中的应用程序,有独立的内存空间和系统资源
2.线程
线程是CPU运算的最小单位,包含在进程之中
3. 进程和线程的关系
线程是包含在进程之中的,一个进程至少包含一个线程,否则无法执行
进程和线程的关系,就像车身和车轮,不是越多越好,要结合实际的硬件环境
4. 线程的执行
多线程在单核心CPU下是轮流交替执行,因为每个线程执行的时间较短,切换频率较高,所以我们感知不到这个过程,宏观上感知就是"同时"执行的,实际上是轮流随机交替执行。
5. 并发和并行
并发:同时发生,轮流交替执行
并行:真正意义上的同时执行
6. 线程的名称
java.lang.Thread类 线程类
main线程为主线程
getName()获取线程名
setName()设置线程名
currentThread()表示获取当前线程对象
package com.qfedu.test3;
/**
* java.lang.Thread类 线程类
* main线程为主线程
* @author WHD
*
*/
public class TestThread1 {
public static void main(String[] args) {
Thread th1 = Thread.currentThread();
System.out.println("当前线程名称:" + th1.getName());
th1.setName("主线程");
System.out.println("当前线程名称:" + th1.getName());
}
}
7.线程的创建
7.1 继承Thread类
创建线程方式1:继承Thread类 重写run方法
run方法中为线程执行的逻辑代码
面试题:调用start方法和调用run方法的区别?
调用start方法会创建新的线程,调用run方法不会创建新的线程,依然使用main线程
package com.qfedu.test3;
/**
* 创建线程方式1:继承Thread类 重写run方法
* run方法中为线程执行的逻辑代码
*
* 面试题:调用start方法和调用run方法的区别?
* 调用start方法会创建新的线程,调用run方法不会创建新的线程,依然使用main线程
* @author WHD
*
*/
public class MyThread1 extends Thread{
@Override
public void run() {
// 线程的默认名称为 Thread-0 -1 -2
System.out.println("当前线程的名称是:" + Thread.currentThread().getName());
}
public static void main(String[] args) {
MyThread1 mt1 = new MyThread1();
mt1.run();
}
}
7.2 实现Runnable接口
创建线程方式2:实现Runnable接口 重写run方法
Runnable不能直接创建线程对象 需要作为参数传入Thread类构造方法中 来创建对象
package com.qfedu.test4;
/**
* 创建线程方式2:实现Runnable接口 重写run方法
* Runnable不能直接创建线程对象 需要作为参数传入Thread类构造方法中 来创建对象
* @author WHD
*
*/
public class MyThread1 implements Runnable{
@Override
public void run() {
for (int j = 0; j < 10; j++) {
System.out.println("当先线程的名称为:" + Thread.currentThread().getName() + "====" + j);
}
}
public static void main(String[] args) {
MyThread1 mt = new MyThread1();
Thread th1 = new Thread(mt, "----线程A----");
Thread th2 = new Thread(mt, "线程B");
th1.start();
th2.start();
}
}
8. 线程的状态
线程的状态:创建–就绪–运行–阻塞–死亡
package com.qfedu.test5;
/**
* 线程的状态:创建--就绪--运行--阻塞--死亡
* @author WHD
*
*/
public class MyThread1 implements Runnable{
@Override
public void run() { // 运行状态
System.out.println("run方法开始执行……");
try {
Thread.sleep(3000); // 阻塞状态
} catch (InterruptedException e) {
e.printStackTrace();
}
for (int j = 0; j < 10; j++) {
System.out.println("当先线程的名称为:" + Thread.currentThread().getName() + "====" + j);
}
} // 死亡状态
public static void main(String[] args) {
MyThread1 mt = new MyThread1();
Thread th1 = new Thread(mt, "----线程A----"); // 创建状态
th1.start(); // 就绪状态
}
}
9. 线程的优先级
线程的优先级:默认为5,最小为1,最大为10
优先级高的线程表示获取CPU资源的概率增大,但是不能保证一定优先执行
设置线程优先级可以使用父类Thread提供的静态常量,也可以直接书写1~10之间的整数数值
package com.qfedu.test5;
/**
* 线程的优先级:默认为5,最小为1,最大为10
* 优先级高的线程表示获取CPU资源的概率增大,但是不能保证一定优先执行
*
* 设置线程优先级可以使用父类Thread提供的静态常量,也可以直接书写1~10之间的整数数值
* @author WHD
*
*/
public class MyThread2 extends Thread{
@Override
public void run() {
for (int i = 0; i < 10; i++) {
System.out.println(Thread.currentThread().getName() + "---" + i );
}
}
public static void main(String[] args) {
MyThread2 th1 = new MyThread2();
MyThread2 th2 = new MyThread2();
th1.setName("---线程A---");
th2.setName("线程B");
System.out.println(th1.getPriority());
System.out.println(th2.getPriority());
th1.setPriority(6);
th2.setPriority(7);
th1.start();
th2.start();
}
}
10. 线程的休眠
线程休眠,属于阻塞状态,表示让当前线程暂停指定时间,然后再继续执行
sleep(long millis)
sleep(long millis,int nanos)
package com.qfedu.test6;
/**
* 线程休眠
* sleep(long millis)
* sleep(long millis,int nanos)
* @author WHD
*
*/
public class MyThread1 extends Thread{
@Override
public void run() {
System.out.println("run方法开始执行");
try {
Thread.sleep(2000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("run方法执行完毕");
}
public static void main(String[] args) {
MyThread1 th1 = new MyThread1();
th1.setName("线程A");
th1.start();
}
}
11.线程的插队
线程的插队 表示当前线程插队执行另外一个线程
join() 无参表示插队线程执行完毕 再执行被插队线程
join(long millis) 有参 毫秒数表示插队线程只能插队指定的时间
join(long millis,it nanos) 有参 毫秒数表示插队线程只能插队指定的时间
package com.qfedu.test6;
/**
* 线程的插队 表示当前线程插队执行另外一个线程
* join() 无参表示插队线程执行完毕 再执行被插队线程
* join(long millis) 有参 毫秒数表示插队线程只能插队指定的时间
* join(long millis,it nanos) 有参 毫秒数表示插队线程只能插队指定的时间
* @author WHD
*
*/
public class MyThread2 extends Thread{
@Override
public void run() {
for (int i = 0; i < 20; i++) {
try {
Thread.sleep(500);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() + "---" + i);
}
}
public static void main(String[] args) throws InterruptedException {
MyThread2 th1 = new MyThread2();
th1.setName("----线程A----");
th1.start();
for (int i = 0; i < 50; i++) {
if(i == 20) {
th1.join(3000);
}
System.out.println(Thread.currentThread().getName() + "----" + i);
}
}
}
12. 线程的礼让
yield() 线程的礼让 只是提供一种可能 并不一定能够保证礼让
package com.qfedu.test6;
/**
* yield() 线程的礼让 只是提供一种可能 并不一定能够保证礼让
* @author WHD
*
*/
public class MyThread3 extends Thread{
@Override
public void run() {
for (int i = 0; i < 20; i++) {
if(i == 10) {
System.out.println("=====线程礼让了=====");
Thread.yield();
}
System.out.println(Thread.currentThread().getName() + "--" + i);
}
}
public static void main(String[] args) {
MyThread3 th1 = new MyThread3();
MyThread3 th2 = new MyThread3();
th1.setName("---线程A---");
th2.setName("线程B");
th1.start();
th2.start();
}
}
13.课堂练习
需求说明:每个线程代表一个人,可设置每人爬山速度,每爬完100米显示信息,爬到终点时给出相应提示
分析:
1.先继承Thread类,重写run方法
2.编写3属性 name表示角色名/线程名 length 总长度 time 每爬100米耗时
3.编写构造方法完成三个属性初始化,其中name属性调用父类构造初始化
package com.qfedu.test7;
/**
* 需求说明:每个线程代表一个人,可设置每人爬山速度,每爬完100米显示信息,爬到终点时给出相应提示
* 分析: 1.先继承Thread类,重写run方法
* 2.编写3属性 name表示角色名/线程名 length 总长度 time 每爬100米耗时
* @author WHD
*
*/
public class ClimbThread extends Thread{
String name;
int length;
int time;
public ClimbThread(String name, int length, int time) {
super(name);
this.length = length;
this.time = time;
}
@Override
public void run() {
while(length > 0) {
try {
Thread.sleep(time);
} catch (InterruptedException e) {
e.printStackTrace();
}
length -= 100;
System.out.println(Thread.currentThread().getName() + "爬了100米还剩余" + length + "米");
}
System.out.println("**************恭喜" + Thread.currentThread().getName() + "爬到了山顶**************");
}
public static void main(String[] args) {
ClimbThread oldMan = new ClimbThread("赵四", 1000, 1000);
ClimbThread youngMan = new ClimbThread("练习两年半的练习生", 1000, 500);
oldMan.start();
youngMan.start();
}
}
某科室一天需看普通号50个,特需号10个,
特需号看病时间是普通号的2倍,(耗时不同是指线程休眠时间不同)
开始时普通号和特需号并行叫号,(同时start)
叫到特需号的概率比普通号高,(线程优先级不同)
当普通号叫完第10号时,要求先看完全部特需号,再看普通号(线程插队)
使用多线程模拟这一过程
分析:我们可以使用当前类作为其中一个角色,比如当前类作为特需号,在main方法中主线程作为普通号
package com.qfedu.test7;
/**
* 某科室一天需看普通号50个,特需号10个,
* 特需号看病时间是普通号的2倍,(耗时不同是指线程休眠时间不同)
* 开始时普通号和特需号并行叫号,(同时start)
* 叫到特需号的概率比普通号高,(线程优先级不同)
* 当普通号叫完第10号时,要求先看完全部特需号,再看普通号(线程插队)
* 使用多线程模拟这一过程
*
* 分析:我们可以使用当前类作为其中一个角色,比如当前类作为特需号,在main方法中主线程作为普通号
* @author WHD
*
*/
public class Special extends Thread{
@Override
public void run() {
for (int i = 1; i <= 10; i++) {
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() + i + "号在看病");
}
System.out.println(Thread.currentThread().getName() + "就诊完毕");
}
public static void main(String[] args) throws InterruptedException {
Special s1 = new Special();
s1.setName("****特需号****");
s1.setPriority(MAX_PRIORITY);
s1.start();
Thread.currentThread().setName("普通号");
for (int i = 1; i <= 50; i++) {
Thread.sleep(500);
System.out.println(Thread.currentThread().getName() + i + "号在看病");
if(i == 10) {
s1.join();
}
}
System.out.println(Thread.currentThread().getName() + "看病完毕");
}
}
14.synchronized关键字
14.1 版本1
使用多线程模拟抢票 三个人(三个线程)抢10张票
必须保证:
1.票号不能重复
2.抢到的票不能多于10张
目前存在问题:票一共卖出了30张 总数以及票号都不对
解决方案:使用static修饰
package com.qfedu.test8;
/**
* 使用多线程模拟抢票 三个人(三个线程)抢10张票
* 必须保证:
* 1.票号不能重复
* 2.抢到的票不能多于10张
*
* 目前存在问题:票一共卖出了30张 总数以及票号都不对
* 解决方案:使用static修饰
* @author WHD
*
*/
public class Buyticket1 extends Thread{
int ticketCount = 10;
@Override
public void run() {
while(ticketCount > 0) {
ticketCount--;
System.out.println(Thread.currentThread().getName() + "抢到了第"+ (10 - ticketCount )+"张票,还剩余"+ ticketCount +"张");
}
System.out.println("票售罄了");
}
public static void main(String[] args) {
Buyticket1 zhaosi = new Buyticket1();
Buyticket1 guangkun = new Buyticket1();
Buyticket1 dana = new Buyticket1();
zhaosi.setName("赵四");
guangkun.setName("广坤");
dana.setName("大拿拿");
zhaosi.start();
guangkun.start();
dana.start();
}
}
14.2 版本2
使用多线程模拟抢票 三个人(三个线程)抢10张票
必须保证:
1.票号不能重复
2.抢到的票不能多于10张
目前存在问题:使用static修饰,依然会存在票的总数以及票号是不对的
解决方案:必须保证同一时间只能有一个线程访问票的总数 以及 操作票的数量 才可以实现要求 使用synchronized关键字将需要同步的
代码包括起来
package com.qfedu.test8;
/**
* 使用多线程模拟抢票 三个人(三个线程)抢10张票
* 必须保证:
* 1.票号不能重复
* 2.抢到的票不能多于10张
*
* 目前存在问题:使用static修饰,依然会存在票的总数以及票号是不对的
*
* 解决方案:必须保证同一时间只能有一个线程访问票的总数 以及 操作票的数量 才可以实现要求 使用synchronized关键字将需要同步的
* 代码包括起来
* @author WHD
*
*/
public class Buyticket2 extends Thread{
static int ticketCount = 10;
@Override
public void run() {
while(ticketCount > 0) {
try {
Thread.sleep(500);
} catch (InterruptedException e) {
e.printStackTrace();
}
ticketCount--;
System.out.println(Thread.currentThread().getName() + "抢到了第"+ (10 - ticketCount )+"张票,还剩余"+ ticketCount +"张");
}
System.out.println("票售罄了");
}
public static void main(String[] args) {
Buyticket2 zhaosi = new Buyticket2();
Buyticket2 guangkun = new Buyticket2();
Buyticket2 dana = new Buyticket2();
zhaosi.setName("赵四");
guangkun.setName("广坤");
dana.setName("大拿拿");
zhaosi.start();
guangkun.start();
dana.start();
}
}
14.3 版本3
使用多线程模拟抢票 三个人(三个线程)抢10张票
必须保证:
1.票号不能重复
2.抢到的票不能多于10张
synchronized关键字:
单词:同步
适用场景:
- 修饰代码块:表示此代码块同时只能有一个线程访问
- 修饰方法:表示此方法同时只能有一个线程访问
package com.qfedu.test8;
/**
* 使用多线程模拟抢票 三个人(三个线程)抢10张票
* 必须保证:
* 1.票号不能重复
* 2.抢到的票不能多于10张
*
* synchronized关键字:
* 单词:同步
* 适用场景:
* 修饰代码块:表示此代码块同时只能有一个线程访问
* 修饰方法:表示此方法同时只能有一个线程访问
*
* @author WHD
*
*/
public class Buyticket3 implements Runnable{
int ticketCount = 10;
@Override
public void run() {
while(true) {
try {
Thread.sleep(500);
} catch (InterruptedException e) {
e.printStackTrace();
}
synchronized(this) {
if(ticketCount == 0) {
break;
}
ticketCount--;
System.out.println(Thread.currentThread().getName() + "抢到了第"+ (10 - ticketCount )+"张票,还剩余"+ ticketCount +"张");
}
}
System.out.println("票售罄了");
}
public static void main(String[] args) {
Buyticket3 bt3 = new Buyticket3();
Thread th1 = new Thread(bt3,"赵四");
Thread th2 = new Thread(bt3,"广坤");
Thread th3 = new Thread(bt3,"大拿");
th1.start();
th2.start();
th3.start();
}
}
14.4 synchronized修饰方法
package com.qfedu.test8;
/**
* synchronized关键字修饰方法
* @author WHD
*
*/
public class Buyticket5 implements Runnable{
int ticketCount = 10;
Object obj = new Object();
@Override
public synchronized void run() {
while(true) {
if(ticketCount == 0) {
break;
}
ticketCount--;
System.out.println(Thread.currentThread().getName() + "抢到了第"+ (10 - ticketCount )+"张票,还剩余"+ ticketCount +"张");
}
System.out.println("票售罄了");
}
public static void main(String[] args) {
Buyticket5 bt3 = new Buyticket5();
Thread th1 = new Thread(bt3,"赵四");
Thread th2 = new Thread(bt3,"广坤");
Thread th3 = new Thread(bt3,"大拿");
th1.start();
th2.start();
th3.start();
}
}
15.synchronized关键字补充
15.1 为什么同步代码块中写this,表示什么含义,能不能写其他的对象?
不是必须写this,只需要保证多个线程共享的是同一个资源就可以了,可以写其他对象,但是通常写this
synchronized:可修饰,静态代码方法、实例方法、代码块(代表锁类、实例、具体代码块 锁的范围递减的,(锁的范围越小越好))
package com.qfedu.test8;
/**
* 为什么同步代码块中写this,表示什么含义,能不能写其他的对象?
* 不是必须写this,只需要保证多个线程共享的是同一个资源就可以了,可以写其他对象,但是通常写this
* @author WHD
*
*/
public class Buyticket4 implements Runnable{
int ticketCount = 10;
Object obj = new Object();
@Override
public void run() {
while(true) {
try {
Thread.sleep(500);
} catch (InterruptedException e) {
e.printStackTrace();
}
synchronized(this) {
if(ticketCount == 0) {
break;
}
ticketCount--;
System.out.println(Thread.currentThread().getName() + "抢到了第"+ (10 - ticketCount )+"张票,还剩余"+ ticketCount +"张");
}
}
System.out.println("票售罄了");
}
public static void main(String[] args) {
Buyticket4 bt3 = new Buyticket4();
Thread th1 = new Thread(bt3,"赵四");
Thread th2 = new Thread(bt3,"广坤");
Thread th3 = new Thread(bt3,"大拿");
th1.start();
th2.start();
th3.start();
}
}
15.2 规则
同一时刻只能有一个线程进入synchronized(this)同步代码块
当一个线程访问一个synchronized(this)同步代码块时,其他synchronized(this)同步代码块同样被锁定
当一个线程访问一个synchronized(this)同步代码块时,其他线程可以访问该资源的非synchronized(this)同步代码
package com.qfedu.test8;
/**
* 同一时刻只能有一个线程进入synchronized(this)同步代码块
* 当一个线程访问一个synchronized(this)同步代码块时,其他synchronized(this)同步代码块同样被锁定
* 当一个线程访问一个synchronized(this)同步代码块时,其他线程可以访问该资源的非synchronized(this)同步代码
* @author WHD
*
*/
public class Test extends Thread{
@Override
public void run() {
System.out.println("非同步代码");
synchronized (this) {
System.out.println("同步代码块1");
}
synchronized (this) {
System.out.println("同步代码块2");
}
}
}
15.3 线程安全的类
Vector Hashtable StringBuffer 这三个类均使用synchronized关键字修饰方法来实现线程安全