本文主要分析jvm中涉及的线程状态以及这些状态的流转关系
wait和notify方法的执行前提是调用该方法的线程必须拿到该对象的监视器锁
点开Thread类中的内部枚举类Thread#State可以见注释,jvm中的线程状态主要分为以下六种:
NEW (还没有启动的线程)
RUNNABLE (在jvm中执行)
BLOCKED (被阻塞,等待获取监视器锁)
WAITTING (无限期地等待另一个线程执行特定操作,如notify)
TIMED_WAITTING (等待另一个线程执行操作长达指定等待时间)
TERMINATED (已退出)
一个线程在同一时刻有且只能有一种状态,该状态属于jvm虚拟机范畴内的状态,不反映任何操作系统层面的线程状态。
这6中线程状态中,我们主要关注以下三种状态:BLOCKED、WAITTING、TIMED_WAITTING。
等待锁的时候会被阻塞,获取到锁后变为RUNNABLE状态
当线程调用wait方法、join方法以及park方法后进入挂起状态,相应的调用notify方法以及unpark方法后恢复为RUNNABLE状态。
与WAITTING状态基本一致,区别在于挂起的状态有超时时间。
需要注意,RUNNABLE状态实际细分为ready和running状态,线程刚start时不会直接获取到cpu的时间片执行任务,而是处于就绪状态(ready)被统一放入就绪队列中,等待着jvm的线程调度器进行调度,只有真正被调度起来才是running状态
还是参考Object类的源码:
wait方法:
notify方法:
可见,wait和notify方法的执行前提是调用该方法的线程必须拿到该对象的监视器锁,否则必定会抛出IllegalMonitorStateException
异常,这是jvm的规定。
那么wait和notify方法配合使用的正确打开方式如下:
public class TestThreadState {
public static void main(String[] args) throws InterruptedException {
final long SLEEP = 1L;
final Object lock = new Object();
new Thread(() -> {
long start = System.currentTimeMillis();
synchronized (lock) {
try {
System.out.println("线程1获取到锁");
TimeUnit.SECONDS.sleep(SLEEP);
System.out.println("线程1即将调用wait方法,挂起当前线程并释放锁");
// 执行wait方法,挂起当前线程并释放锁
lock.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
System.out.println("线程1执行耗时:" + (System.currentTimeMillis() - start));
}).start();
// 让线程1获取到时间片获取到监视器锁(注意这里和线程1睡眠保持了一致)
TimeUnit.SECONDS.sleep(SLEEP);
new Thread(() -> {
synchronized (lock) {
try {
System.out.println("线程2获取到锁");
TimeUnit.SECONDS.sleep(3);
lock.notify();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}).start();
}
}
执行结果打印:
线程1获取到锁
线程1即将调用wait方法,挂起当前线程并释放锁
线程2获取到锁
线程1执行耗时:4015
执行代码可以看到流程为:
1、线程1调用wait方法使得当前线程挂起并释放监视器锁
2、线程2这时候获取到锁,进入同步块执行业务,结束后调用notify方法唤醒其他线程并退出同步块,释放监视器锁。
3、线程1恢复到RUNNABLE状态