前言:线程的五种状态
本文是线程篇的一个分支,主要结合我的理解,看一下sleep和wait以及线程的一些状态
网上的图看起来都有点丑,我自己画了一幅:
1.New: 新建态: new Thread ~ thread.start期间2.Runnable: 可执行态: 可被CPU调度执行期间。3.Running 运行态: 线程获取CPU权限进行执行4.Blocked 阻塞状态: 阻塞状态是线程因为某种原因放弃CPU使用权,暂时停止运行。 |---等待阻塞:通过调用线程的wait()方法,让线程等待某工作的完成。 |---同步阻塞:线程在获取synchronized同步锁失败(因为锁被其它线程所占用)时 |---其他阻塞:通过调用线程的sleep()或join()或发出了I/O请求时5.Dead 死亡状态: 线程执行完了或者因异常退出了run()方法,该线程结束生命周期。复制代码
一、Thread.sleep简述
暂停当前线程,进入Blocked状态,把cpu片段让出给其他线程。
1.测试代码:
public class Main0 { static SimpleDateFormat sdf = new SimpleDateFormat("mm:ss"); public static void main(String[] args) { new Thread(new Car()).start(); try { Thread.sleep(2000); } catch (InterruptedException e) { e.printStackTrace(); } new Thread(new Ambulance()).start(); } private static class Car implements Runnable { @Override public void run() { System.out.println(sdf.format(System.currentTimeMillis()) + ":小汽车开始启动,在路上跑"); System.out.println(sdf.format(System.currentTimeMillis()) + ":小汽车跑到终点"); } } private static class Ambulance implements Runnable { @Override public void run() { System.out.println(sdf.format(System.currentTimeMillis()) + ":救护车开始启动,在路上跑"); System.out.println(sdf.format(System.currentTimeMillis()) + ":救护车跑到终点"); } }}复制代码
2. 结果分析:注02:29代表当前时刻的分秒,即2分29秒
---->[运行结果]----------------------02:29:小汽车开始启动,在路上跑02:29:小汽车跑到终点02:31:救护车开始启动,在路上跑02:31:救护车跑到终点复制代码
二、线程内sleep以及synchronized
1.子线程加入休眠
private static class Car implements Runnable { @Override public void run() { System.out.println(sdf.format(System.currentTimeMillis()) + ":小汽车开始启动,在路上跑"); try { Thread.sleep(5000); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println(sdf.format(System.currentTimeMillis()) + ":小汽车跑到终点"); }}复制代码
2.结果分析
Car线程的任务是睡5s,可见主线程的休眠没有影响到Car子线程的运行(休眠)
18:48:小汽车开始启动,在路上跑18:50:救护车开始启动,在路上跑18:50:救护车跑到终点18:53:小汽车跑到终点复制代码
3.当加锁睡眠时
在线程1中加synchronized(
这里锁用sdf对象,你也可以任意
)
public class Main2 { static SimpleDateFormat sdf = new SimpleDateFormat("mm:ss"); public static void main(String[] args) { new Thread(new Car()).start(); try { Thread.sleep(2000); } catch (InterruptedException e) { e.printStackTrace(); } new Thread(new Ambulance()).start(); } private static class Car implements Runnable { @Override public void run() { synchronized (sdf){ System.out.println(sdf.format(System.currentTimeMillis()) + ":小汽车开始启动,在路上跑"); try { Thread.sleep(5000); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println(sdf.format(System.currentTimeMillis()) + ":小汽车跑到终点"); } } } private static class Ambulance implements Runnable { @Override public void run() { synchronized (sdf){ System.out.println(sdf.format(System.currentTimeMillis()) + ":救护车开始启动,在路上跑"); System.out.println(sdf.format(System.currentTimeMillis()) + ":救护车跑到终点"); } } }}复制代码
2.结果分析
Car线程和Ambulance线程在运行时使用同一把锁,线程在休眠时不会释放锁
所以Ambulance线程需要等待Car线程执行完成,才能进行执行
23:46:小汽车开始启动,在路上跑23:51:小汽车跑到终点23:51:救护车开始启动,在路上跑23:51:救护车跑到终点复制代码
三、Object#wait()
方法的作用
在
直到其他线程调用A对象的线程t1
调用A对象
的wait()方法
,会释放t1持有的锁
,让t1进入等待队列(Blocked状态)
notify()方法
或notifyAll()
方法t1进入同步队列(Blocked状态)
当t1获得锁后会进入就绪状态Runnable
,获取CPU的调度权后会继续执行,再贴一遍这个图:
1.wait方法的使用
既然是释放当前线程的锁,那么不须有锁才行,而且必须用该锁的对象调用wait方法
比如上面是用sdf对象加锁的,必须使用sdf.wait();
,否则会抛出InterruptedException
private static class Car implements Runnable { @Override public void run() { synchronized (sdf) { System.out.println(sdf.format(System.currentTimeMillis()) + ":小汽车开始启动,在路上跑"); try { Thread.sleep(3000);//模拟执行3s的任务之后 } catch (InterruptedException e) { e.printStackTrace(); } try { System.out.println(sdf.format(System.currentTimeMillis()) + ":小汽车紧急刹车...."); sdf.wait(); System.out.println(sdf.format(System.currentTimeMillis()) + ":小汽车开始启动...."); } catch (InterruptedException e) { e.printStackTrace(); } try { Thread.sleep(5000); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println(sdf.format(System.currentTimeMillis()) + ":小汽车跑到终点"); } }}private static class Ambulance implements Runnable { @Override public void run() { synchronized (sdf) { System.out.println(sdf.format(System.currentTimeMillis()) + ":救护车开始启动,在路上跑"); System.out.println(sdf.format(System.currentTimeMillis()) + ":救护车跑到终点"); } }}复制代码
2.结果分析
在Car线程调用
如果不唤醒线程,线程将一直阻塞,就是sdf.wait();
后,锁将被释放,然后Ambulance线程就可以持有锁运行了根本停不下来
。打个比方就是sdf是司机wait()
之后就把车钥匙扔了,然后熄火了。钥匙拿不回来,车就跑不了。需要notify()
获取车钥匙
30:48:小汽车开始启动,在路上跑30:51:小汽车紧急刹车....30:51:救护车开始启动,在路上跑30:51:救护车跑到终点然后就阻塞在这里停不下来了....复制代码
3.唤醒等待线程
注意wait和notify需要使用同一个对象,否则然并卵
在Ambulance线程跑完后唤醒Car线程,然后Car获取所后会进入就绪态
private static class Ambulance implements Runnable { @Override public void run() { synchronized (sdf) { System.out.println(sdf.format(System.currentTimeMillis()) + ":救护车开始启动,在路上跑"); System.out.println(sdf.format(System.currentTimeMillis()) + ":救护车跑到终点"); sdf.notify(); } }}复制代码
4.结果分析
40:23:小汽车开始启动,在路上跑40:26:小汽车紧急刹车....40:26:救护车开始启动,在路上跑40:26:救护车跑到终点40:26:救护车:喂,哥们,醒醒,你可以开了...40:26:小汽车开始启动....40:31:小汽车跑到终点复制代码
四、Thread#join()
方法 和Thread#yield
1.普通代码
public class Main6 { static SimpleDateFormat sdf = new SimpleDateFormat("mm:ss"); public static void main(String[] args) { Thread car = new Thread(new Car()); car.start(); System.out.println(sdf.format(System.currentTimeMillis()) + ":main线程结束"); } private static class Car implements Runnable { @Override public void run() { System.out.println(sdf.format(System.currentTimeMillis()) + ":小汽车开始启动,在路上跑"); try { Thread.sleep(3000);//模拟执行3s的任务之后 } catch (InterruptedException e) { e.printStackTrace(); } System.out.println(sdf.format(System.currentTimeMillis()) + ":小汽车跑到终点"); } }}复制代码
2.结果分析
26:28:小汽车开始启动,在路上跑26:28:main线程结束26:31:小汽车跑到终点复制代码
3.join方法的使用
阻塞当前线程,知道join的线程结束或超时
public class Main6 { static SimpleDateFormat sdf = new SimpleDateFormat("mm:ss"); public static void main(String[] args) { Thread car = new Thread(new Car()); car.start(); try { car.join(); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println(sdf.format(System.currentTimeMillis()) + ":main线程结束"); } private static class Car implements Runnable { @Override public void run() { System.out.println(sdf.format(System.currentTimeMillis()) + ":小汽车开始启动,在路上跑"); try { Thread.sleep(3000);//模拟执行3s的任务之后 } catch (InterruptedException e) { e.printStackTrace(); } System.out.println(sdf.format(System.currentTimeMillis()) + ":小汽车跑到终点"); } }}---->[打印结果]-----------------------------31:53:小汽车开始启动,在路上跑31:56:小汽车跑到终点31:56:main线程结束复制代码
4.yield方法测试
让出线程的cpu调度权,之后再一起抢夺。
运行状态-->就绪状态
public class Main7 { public static void main(String[] args) { Thread car = new Car("car"); Thread ambulance = new Car("Ambulance"); car.start(); ambulance.start(); } private static class Car extends Thread { public Car(String name) { super(name); } @Override public void run() { for (int i = 0; i < 10; i++) { System.out.println(getName() + "----" + i); if (i == 5) { yield(); } } } }}复制代码
运行了几组数据,大部分满足让出调度权后,另一个线程执行,也有少量情况不是。
所以yield说明我现在不急,可以划划水,执行权可以让出去。不过接下来谁执行还是不定的。
五、小结
1.需要补充的点:
1.关于synchronized锁这里不展开,不了解的可以见:
2.关于synchronized锁对象需要一致,否则锁不住,然并卵。常用class对象,如Car.class
3.可以设置超时唤醒xxx.wait(3000)
,即3s后唤醒线程。 4.可以设置join超时xxx.join(3000)
,即3s后线程进入就绪态。 5.notifyAll:来着黑暗寒冬的随从们,仆人们,士兵们,听从克尔苏加德的召唤吧!
2.简单比较
item | 从属 | 是否释放锁 | 状态转变为 | 异常 |
---|---|---|---|---|
sleep | Thread 静态方法 | NO | 阻塞 | InterruptedException |
wait | Object 公共方法 | YES | 等待队列 | IllegalMonitorStateException+InterruptedException |
notify/notifyAll | Object 公共方法 | -- | 同步队列 | IllegalMonitorStateException |
join | Thread 公共方法 | NO | 阻塞 | InterruptedException |
yield | Thread 公共方法 | NO | 可执行态 | 无 |