状态切换
线程生命周期.png数字并不表示先后顺序
阻塞
三种,同步阻塞,等待阻塞,其他阻塞。
同步阻塞(Entry Set),等待获取对象锁的同步队列,有些地方也叫锁池(每个对象都有一个内部锁,monitor监视器)。当某个线程访问一个对象中的Synchronize同步代码块时,如果发现该对象的监视器被别的线程持有,就进入该对象的 Entry Set,当前线程状态变成 BLOCKED。
等待阻塞(Wait Set),占有某个对象监视器的线程,调用该对象的 wait() 方法放弃监视器,就进入了该对象的 Wait Set,当前线程状态变成 WAITING(on object monitor)。
其他阻塞,调用sleep等方法进入阻塞状态,让出CPU,但不会放弃锁,当前线程进入 TIME_WAITING(sleeping) 状态。
通过 jstack 命令查看线程状态变化
package com.xieyupeng.springboot.Multithreading;
public class ObjectWaitTest {
synchronized void objectWait() throws InterruptedException{
System.out.println(Thread.currentThread().getName() + "进来先休息一下");
Thread.sleep(10000);
System.out.println(Thread.currentThread().getName() + "休息完了放弃锁");
this.wait();
System.out.println(Thread.currentThread().getName() + "获得锁");
Thread.sleep(5000);
}
synchronized void objectNotify() throws InterruptedException {
this.notifyAll();
System.out.println(Thread.currentThread().getName()
+"唤醒所有线程进入锁池并选择一个获取锁");
Thread.sleep(10000);
}
public static void main(String[] args) throws InterruptedException {
ObjectWaitTest test = new ObjectWaitTest();
for (int i = 0 ; i < 3 ; i ++){
new Thread(()->
{
try {
System.out.println(Thread.currentThread().getName()+"调用同步块");
test.objectWait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}).start();
}
Thread.sleep(40000); //保证3个线程代码都执行了 wait方法
test.objectNotify();
}
}
执行上面的 main 函数,依次记录状态如下:
- 0,1,2 三个线程依次调用同步块,0线程最先获取 test 对象的锁,1、2线程进入同步阻塞。
此时执行 jstack 命令,可以看到,0线程先占有了 test 对象的监视器,锁住了<0x00000000d675a770>地址,进入了代码块,执行过程中,调用了 sleep() 方法,状态变成了 TIMED_WAITING。同一时间,1、2线程由于获取不到 test 对象的监视器,进入了 test 对象的 Entry Set(waiting for monitor entry),一直等待给<0x00000000d675a770>地址上锁,状态变成 BLOCKED。3个线程都是对同一个地址的竞争,从这个地址后面括号里的描述也可以看出,就是和 test 对象相关的。如果这里使用 Lock 上锁的话,线程状态是 WAITING(parking)。
A.png- 0线程 sleep 完了,调用了 wait() 方法放弃了 test 对象的监视器,进入等待阻塞;接着,2线程获得了 test 对象的监视器。 上一步中,2线程是比1线程 后进入阻塞队列的,也就是说,谁能在下一次获取对象的锁,不是由进入 Entry Set 的顺序决定的,还和其他因素有关,可以暂时理解为随机。对应线程图中的6。
通过 jstack 命令查看,in Object.wait(),0线程 调用了 test.wait(),线程状态切换成 WAITING,而2线程的状态 和 上一步的 0线程的一样,1线程依旧在Entry Set中。
B.png- 3个线程都执行了 wait() 方法,都进入了 Wait Set,线程状态WAITING(on object monitor)。如果这里调用的是 Condition 的 wait() 方法的话,线程状态就是 WAITING(parking)
从线程 dump 文件中可以看出,全部变成了 WAITING 状态。图中箭头方向是程序运行的时间方向。比如2线程,首先执行了 run 方法,然后获得了对象锁,进入了同步方法 objectWait,接着调用 wait() 方法,进入等待阻塞队列。
C.png- 主线程调用 test.notifyAll() ,唤醒所有调用过 test.wait() 进入等待阻塞的线程。执行对象的 nofity()/notifAll() 方法,必须要等到 synchronize 同步代码执行完毕才能获得锁。唤醒方法中特意加了一个 Thread.sleep(10000),就是为了测试这个。4图可以看出执行了 notifyAll() 方法,对应的 D图中,线程都变成了BLOCKED,说明它们被唤醒去竞争 test 对象锁了,但是锁仍然被主线程占据了,因为它还在 sleep,没有退出同步代码,所以线程们都被阻塞了。
- 线程被唤醒后,只能有一个线程获取对象锁,所以又要竞争了,竞争成功变成就绪态,失败的仍然留在Wait Set,状态变成 BLOCKED。
从线程dump文件中可以看出,1线程获得了锁,但是执行了 sleep() 变成了 TIME_WAITING状态,说明是一个有时间期限的等待,waiting on condition 等待一个条件让它退出阻塞状态,这个条件就是时间到期。0和2线程是阻塞状态,但是从 in Object wait() 可以看出,它们还在 Wait Set 中。
E.png-
程序执行完毕
6.png
参考:
https://www.zhihu.com/question/64725629/answer/224047354
https://www.cnblogs.com/zhengyun_ustc/archive/2013/01/06/dumpanalysis.html
网友评论