结合jvisualvm一次性看清线程状态
前言
本文主要结合jvisualvm工具和thread自带的getState方法,分析不同情况下的线程状态
其中jvisualvm区分的线程状态区分如下
![](https://img.haomeiwen.com/i9112801/107e0dc3336f4cd0.png)
jvm的线程状态区分如下
public enum State {
NEW, // 新建的
RUNNABLE, // 可运行的
BLOCKED, // 阻塞的,等待在synchronized上的线程
WAITING, // 等待被其它线程唤醒,通过Object#wait(),Thread#join(),LockSupport#park()都会进入这个状态
TIMED_WAITING, // 等待固定时间,通过Object#wait(long),Thread#join(long),LockSupport#parkNanos,LockSupport#parkUntil会进入这个状态
TERMINATED; // 结束
}
二者并非一一对应,区分的角度有点差别
sleep
先来最简单常用的sleep方法,顾名思义,就是让线程睡一会并指定醒来时间,并且释放cpu资源
public class SleepTest {
public static void main(String[] args) throws InterruptedException {
Thread thread = new Thread(() -> {
try {
TimeUnit.MINUTES.sleep(1);
} catch (InterruptedException e) {
e.printStackTrace();
}
}, "th-1");
thread.start();
TimeUnit.SECONDS.sleep(5);
System.out.println("线程的状态:" + thread.getState());
}
}
由于sleep是等待固定时间,所以其状态输出:TIMED_WAITING
在jvisualvm中sleep对应的状态是:休眠
![](https://img.haomeiwen.com/i9112801/c603c3492a24ad08.png)
使用sleep需要处理一个checked异常InterruptedException,这个接下来说
interrupt
interrupt字面意思上讲就是打断当前的干的事,但是没有实际上的作用,只是修改了线程的一个状态,换句话说,如果被打断的线程不理会,打断等于无效
我们用代码模拟一个家长喊在外面玩耍的熊孩子吃饭的故事
public class InterruptTest {
public static void main(String[] args) throws InterruptedException {
Thread thread = new Thread(() -> {
System.out.println("孩子正在玩...");
String game = "好玩";
while ("好玩".equals(game)) {
}
System.out.println("孩子回家吃饭: " + Thread.currentThread().isInterrupted());
}, "son");
thread.start();
TimeUnit.SECONDS.sleep(3);
System.out.println("孩子的状态:" + thread.getState());
System.out.println("家长喊孩子回家吃饭");
thread.interrupt();
while (true) {}
}
}
代码输出:
孩子正在玩...
孩子的状态:RUNNABLE
家长喊孩子回家吃饭
孩子在外面玩,家长试图通过interrupt
打断孩子并叫孩子回家吃饭,但孩子还是该干嘛干嘛,没有执行“回家吃饭”的代码
那么interrupt
有什么用呐?大部分情况是结合isInterrupted
或interrupted
使用,二者可以查看线程是否被打断,也就是说interrupt
相当于发送一个打断信号,isInterrupted
可以获取打断信号,至于收到打断信号后如何处理用户代码自己决定
模拟一个听话的孩子,看到了打断信号,就开始放弃玩耍回家吃饭
public class IsInterruptedTest {
public static void main(String[] args) throws InterruptedException {
Thread thread = new Thread(() -> {
System.out.println("孩子正在玩...");
while (!Thread.currentThread().isInterrupted()) { // 时刻查看自己是否被打断
}
System.out.println("孩子回家吃饭: " + Thread.currentThread().isInterrupted());
}, "son");
thread.start();
TimeUnit.SECONDS.sleep(3);
System.out.println("孩子的状态:" + thread.getState());
System.out.println("家长喊孩子回家吃饭");
thread.interrupt();
}
}
输出:
孩子正在玩...
孩子的状态:RUNNABLE
家长喊孩子回家吃饭
孩子回家吃饭: true
上面用的isInterrupted
,也可以使用Thread.interrupted()
,后者处理返回是否打断,还会清除打断信号,上面的循环代码修改为
while (!Thread.interrupted()) {
}
System.out.println("孩子回家吃饭: " + Thread.currentThread().isInterrupted());
结果只有最后一行有变化
孩子回家吃饭: false
变为false是因为执行Thread.interrupted()
把打断信号清除了
上文讲到的sleep()
方法默认响应打断,而响应的方式就是抛出InterruptedException
异常,这就是为什么我们使用sleep()
一定要处理InterruptedException
异常
下面模拟一个孩子在外面睡觉的场景
public class InterruptedExceptionTest {
public static void main(String[] args) throws InterruptedException {
Thread thread = new Thread(() -> {
System.out.println("孩子在外面睡觉...");
try {
TimeUnit.SECONDS.sleep(60);
} catch (InterruptedException e) {
System.out.println("孩子回家吃饭: " + Thread.currentThread().isInterrupted());
}
}, "son");
thread.start();
TimeUnit.SECONDS.sleep(3);
System.out.println("孩子的状态:" + thread.getState());
System.out.println("家长喊孩子回家吃饭");
thread.interrupt();
while (true) {}
}
}
输出
孩子在外面睡觉...
孩子的状态:TIMED_WAITING
家长喊孩子回家吃饭
孩子回家吃饭: false
最后一条出现false,说明sleep也会先擦除打断信息再抛出异常
最后一个睡觉的案例孩子的状态是TIMED_WAITING
上面已经介绍过,其余孩子正在玩的例子中孩子的状态都是RUNNABLE
,这个状态代表线程是可运行的,至于是否真实正在运行,是操作系统的cpu调度决定的,再jvisualvm中,状态就是运行
![](https://img.haomeiwen.com/i9112801/c67ebb0dd2c4c139.png)
join
join的意思是等待某线程结束,那基本上对应的状态就是WAITING
没跑了,试一下
模拟一个家长等孩子睡醒吃饭
public class JoinTest {
public static void main(String[] args) throws InterruptedException {
Thread pThread = Thread.currentThread();
Thread thread = new Thread(() -> {
System.out.println("孩子正睡觉...");
try {
TimeUnit.SECONDS.sleep(10);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("孩子醒来看家长状态:" + pThread.getState());
System.out.println("孩子洗脸刷牙...");
int i = 0;
while (++i>0) {
new Object();
// 模拟刷牙过程
}
System.out.println("孩子起来了");
}, "son");
thread.start();
System.out.println("家长等孩子醒...");
thread.join();
System.out.println("开始吃饭");
}
}
输出
家长等孩子醒...
孩子正睡觉...
孩子醒来看家长状态:WAITING
孩子洗脸刷牙...
孩子起来了
开始吃饭
不出意料,孩子醒来看家长状态是WAITING
,再看一下jvisualvm
![](https://img.haomeiwen.com/i9112801/221a70ef142d76a4.png)
join过程jvisualvm的显示是黄色:等待
join也响应interrupte,和sleep一样抛出异常InterruptedException
synchronized
这个作用就不多说了,模拟一下两个线程运行同一个synchronized修饰的方法
public class SyncTest {
public static void main(String[] args) throws InterruptedException {
Thread thread1 = new Thread(() -> {
doRun();
}, "th-1");
thread1.start();
Thread thread2 = new Thread(() -> {
try {
TimeUnit.SECONDS.sleep(1);
} catch (InterruptedException e) {
e.printStackTrace();
}
doRun();
}, "th-2");
thread2.start();
TimeUnit.SECONDS.sleep(3);
System.out.println("th-2的状态:" + thread2.getState());
thread2.interrupt(); // 尝试打断
TimeUnit.SECONDS.sleep(3);
System.out.println("th-2的状态:" + thread2.getState());
}
public synchronized static void doRun() {
while (true){}
} // 模拟执行时间很长
}
输出
th-2的状态:BLOCKED
th-2的状态:BLOCKED
即在synchronized上等待锁的线程的状态是BLOCKED
,而且synchronized不响应interrupte,而jvisualvm中,这种状态是粉色的监视
![](https://img.haomeiwen.com/i9112801/4d3f001665e4da69.png)
ReentrantLock
接下来看同样是锁工具的ReentrantLock,修改上面代码使用ReentrantLock
public class LockTest {
public static void main(String[] args) throws InterruptedException {
Lock lock = new ReentrantLock();
Thread thread1 = new Thread(() -> {
lock.lock(); // 拿到锁不释放
}, "th-1");
thread1.start();
Thread thread2 = new Thread(() -> {
try {
TimeUnit.SECONDS.sleep(1);
} catch (InterruptedException e) {
e.printStackTrace();
}
lock.lock();
}, "th-2");
thread2.start();
TimeUnit.SECONDS.sleep(3);
System.out.println("th-2的状态:" + thread2.getState());
thread2.interrupt();
TimeUnit.SECONDS.sleep(3);
System.out.println("th-2的状态:" + thread2.getState());
}
}
输出
th-2的状态:WAITING
th-2的状态:WAITING
可以看到等待lock的线程状态是WAITING
,和上面的join一样,再看jvisualvm
![](https://img.haomeiwen.com/i9112801/b48ed8e7140894cd.png)
对应的是橙黄色的
驻留
,可以在jvm中join和lock以及后面讲的wait都是WAITING
,而在jvisualvm中细分了一下lock这种等待叫做驻留
上面的第二条输出证明等待在lock()上的线程也不响应interrupt
实际上ReentrantLock内部是使用Unsafe.park
方法让线程进行等待的
Unsafe.park
也是一种让线程等待的方法,并需要其他线程通过unpark唤醒,测试一下
家长喊休息的孩子起来吃饭
public class ParkTest {
public static void main(String[] args) throws Exception {
// 获得unsafe
Field f = Unsafe.class.getDeclaredField("theUnsafe");
f.setAccessible(true);
Unsafe unsafe = (Unsafe) f.get(null);
// 开始
Thread thread = new Thread(() -> {
System.out.println("孩子正在休息");
unsafe.park(false, 0);
System.out.println("孩子被唤醒: " + Thread.currentThread().isInterrupted());
}, "son");
thread.start();
TimeUnit.SECONDS.sleep(3);
System.out.println("孩子的状态:" + thread.getState());
System.out.println("家长喊孩子起来吃饭");
// thread.interrupt();
unsafe.unpark(thread);
}
}
输出
孩子正在休息
孩子的状态:WAITING
家长喊孩子起来吃饭
孩子被唤醒: false
结果必然是WAITING
,jvisualvm肯定和lock一样:驻留
(lock内部使用就是unsafe.park)
![](https://img.haomeiwen.com/i9112801/45e4311982e05948.png)
把unsafe.unpark(thread)
改成thread.interrupt()
试试打断,输出
孩子正在休息
孩子的状态:WAITING
家长喊孩子起来吃饭
孩子被唤醒: true
可以看到unsafe.park响应interrupt,效果和unpark一样,且不清除打断标记
但问题来了为啥使用park的lock不响应interrupt?原因是lock的内部做了逻辑处理
wait¬ify
上面讲的join时线程处于等待
状态,其实按字面意思等待
状态最具代表性的应该是wait()方法,但由于它相对复杂点,放在最后说
首先wait和sleep、join都不一样,后二者是属于Thread对象的方法,而wait以及notify是任何对象都有的方法(即Object的方法)
wait 方法是释放对象锁并让当前线程等待在该对象上,直到另一个线程调用了该对象的 notify 或 notifyAll 方法之后,才能继续恢复执行
wait¬ify必须先获得对象锁才能调用(必须在synchronized下),也就是说只有获得对象的锁才有资格在对象上等待,也只有获得锁才有资格通知在对象上等待的线程
下面用代码模拟一个孩子等家长做蛋堡的场景
public class WaitTest {
public static Object o = new Object();
public static void main(String[] args) throws InterruptedException {
Thread thread = new Thread(() -> {
synchronized (o) {
System.out.println("孩子正等待...");
try {
o.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("孩子开始吃蛋堡");
}
}, "son");
thread.start();
TimeUnit.SECONDS.sleep(1);
synchronized (o) {
System.out.println("家长做蛋堡...");
TimeUnit.SECONDS.sleep(3);
System.out.println("孩子的状态:" + thread.getState());
System.out.println("家长做完喊孩子吃");
o.notifyAll();
// System.out.println("收拾桌子");
// TimeUnit.SECONDS.sleep(4);
// System.out.println("孩子的状态:" + thread.getState());
}
while (true) {}
}
}
输出
孩子正等待...
家长做蛋堡...
孩子的状态:WAITING
家长做完喊孩子吃
孩子开始吃蛋堡
以上代码o是一个空对象用来实现锁,它好比一个盘子,孩子和家长的一个约定做完蛋堡就放入盘子,首先孩子获得盘子,调用wait在盘子旁边等待,并交出盘子的控制权(调用wait会释放锁,收到notify时再重新尝试获得锁并继续执行代码)
家长获得盘子,并开始做蛋堡,做完之后通过notifyAll通知盘子旁边等待的孩子来吃
孩子调用wait对应的状态是WAITING
,jvisualvm就是黄色的等待
![](https://img.haomeiwen.com/i9112801/753209e8a0e70c5c.png)
有个小问题,wait¬ify必须在synchronized下执行,wait时会释放锁,那么执行notifyAll也会立即释放锁吗,做个测试把上面代码的注释打开
synchronized (o) {
System.out.println("家长做蛋堡...");
TimeUnit.SECONDS.sleep(3);
System.out.println("孩子的状态:" + thread.getState());
System.out.println("家长做完喊孩子吃");
o.notifyAll();
System.out.println("收拾厨房");
TimeUnit.SECONDS.sleep(3);
System.out.println("孩子的状态:" + thread.getState());
}
输出
孩子正等待...
家长做蛋堡...
孩子的状态:WAITING
家长做完喊孩子吃
收拾厨房
孩子的状态:BLOCKED
孩子开始吃蛋堡
可以看到,家长做完喊孩子吃(调用notifyAll)后,孩子并没有马上开始吃,而是收拾家长完厨房后才开始吃,但孩子的状态由WAITING
变为BLOCKED
,证明收到notify消息是,孩子就已经不再wait了,而是尝试获取锁,也就是等在synchronized外,而上文讲到等待synchronized外的线程是BLOCKED
状态,而最终家长的synchronized代码执行完毕才实际释放锁,孩子开始吃蛋堡,最后对照jvisualvm分析下整个过程
![](https://img.haomeiwen.com/i9112801/3ec3972989271323.png)
所以notifyAll只是唤醒
WAITING
,但WAITING
醒来之后还要再次获得锁,所以如果获取不到就要BLOCKED
阻塞
wait同样响应interrupte,响应方式和join,sleep一样是抛出InterruptedException异常
总结
最后还是总结一下,虽然我觉得这东西靠背肯定没用
- 正在运行的线程是
RUNABLE
状态,在jvisualvm中是绿色运行
- wait()会使线程进入
WAITING
状态,在jvisualvm中是黄色等待
- join()会使线程进入
WAITING
状态,在jvisualvm中是黄色等待
- sleep()会使线程进入
TIMED_WAITING
状态,在jvisualvm中是蓝色休眠
,TIMED_WAITING
是一种特殊的等待,其他的固定时间的等待都是TIMED_WAITING
状态,比如wait(6) - unsafe.park会使线程进入
WAITING
状态,在jvisualvm中是橙色驻留
- synchronized会使线程进入
BLOCKED
状态,在jvisualvm中是粉色监视
- interrupt代表打断,但实际响应需要配合isInterrupted手动实现,以上的方法中wait/join/sleep默认响应抛出异常InterruptedException,unsafe.park响应结束等待,synchronized和ReentrantLock的等待过程不响应