线程状态有哪些?
哪种情况下会响应中断?哪种情况下不响应中断?
在响应中断的方法中,哪些方法会重置中断标志位?哪些方法不会重置中断标志位?
本文会回答上面三个问题,这些也是学习中断的关键。最近在学习JUC框架的时候,发现了很多工具类都是支持可中断的,如AQS、FutureTask都是可以在线程执行中,支持对于中断的响应,所以需要对线程中断有个了解,才能更好的学习JUC的源码。
线程中断可以使一个线程从等待状态变成就绪状态,如果中断的线程正处于运行状态,那么这个中断是不会用任何作用的(表面上不会影响正在运行的线程),线程恢复到就绪状态后,可以继续执行逻辑代码,想要让一个线程从等待状态中恢复过来有三种发送:一.等待超时,二.得到一个通知,三.使用中断
注意:使用线程中断,并不是要把线程给终止或是杀死,而是让线程不再继续等待,而是让线程不再继续等待,线程可以继续往下执行代码,线程发生中断后,会抛出一个中断的异常,决定如何处理就看业务代码怎么写了。
Thread.interrupt()方法仅仅是在当前线程中打了一个停止的标识将中断标志修改为true,并没有真正的停止线程。如果在此基础上进入堵塞状态(sleep(),wait(),join()),马上就会抛出一个InterruptedException,且中断标志被清除,重新设置为false。记住一点,当调用Thread.interrupt(),还没有进行中断时,此时的中断标志位是true,当发生中断之后(执行到sleep(),wait(),join()),这个时候的中断标志位就是false了。
中断是通过调用Thread.interrupt()方法来做的. 这个方法通过修改了被调用线程的中断状态来告知那个线程, 说它被中断了. 对于非阻塞中的线程, 只是改变了中断状态, 即Thread.isInterrupted()将返回true; 对于可取消的阻塞状态中的线程, 比如等待在这些函数上的线程, Thread.sleep(), Object.wait(), Thread.join(), 这个线程收到中断信号后, 会抛出InterruptedException, 同时会把中断状态置回为false.但调用Thread.interrupted()会对中断状态进行复位。
public void interrupt()方法:中断线程,也就是改变中断标志位为true
public static boolean interrupted()方法:静态方法,内部实现是调用的当前线程的isInterrupted(),并且会重置当前线程的中断状态
public boolean isInterrupted()方法:判断线程是否被中断,返回ture代表线程被中断了,只是去获取一下,不会该表中断标志位。
NEW 状态是指线程刚创建,尚未启动,不会出现在Dump中。
RUNNABLE 状态是线程正在正常运行中, 当然可能会有某种耗时计算/IO等待的操作/CPU时间片切换等, 这个状态下发生的等待一般是其他系统资源, 而不是锁, Sleep等,主要不同是runable里面有2个状态,可以理解为就是JVM调用系统线程的状态。
BLOCKED 受阻塞并等待监视器锁。这个状态下, 是在多个线程有同步操作的场景, 比如正在等待另一个线程的synchronized 块的执行释放, 或者可重入的 synchronized块里别人调用wait() 方法, 也就是这里是线程在等待进入临界区
WAITING 无限期等待另一个线程执行特定操作。这个状态下是指线程拥有了某个锁之后, 调用了他的wait方法, 等待其他线程/锁拥有者调用 notify / notifyAll 一遍该线程可以继续下一步操作, 这里要区分 BLOCKED 和 WATING 的区别, 一个是在临界点外面等待进入, 一个是在临界点里面wait等待别人notify, 线程调用了join方法 join了另外的线程的时候, 也会进入WAITING状态, 等待被他join的线程执行结束
TIMED_WAITING 有时限的等待另一个线程的特定操作。这个状态就是有限的(时间限制)的WAITING, 一般出现在调用wait(long), join(long)等情况下, 另外一个线程sleep后, 也会进入TIMED_WAITING状态
TERMINATED 这个状态下表示 该线程的run方法已经执行完毕了, 基本上就等于死亡了(当时如果线程被持久持有, 可能不会被回收)
响应中断的方法: 线程进入等待或是超时等待的状态后,调用interrupt方法都是会响应中断的,所以响应中断的方法:Object.wait()、Thread.join、Thread.sleep、LockSupport.park的有参和无参方法。
不响应中断的方法:线程进入阻塞状态后,是不响应中断的,等待进入synchronized的方法或是代码块,都是会被阻塞的,此时不会响应中断,另外还有一个不响应中断的,那就是阻塞在ReentrantLock.lock方法里面的线程,也是不响应中断的,如果想要响应中断,可以使用ReentrantLock.lockInterruptibly方法。
其ReentrantLock底层是使用LockSupport.park方法进行等待的,前面说了LockSupport.park是响应中断的,当线程进入ReentrantLock.lock方法里面进行阻塞后,此时调用Thread.interrupt()方法之后,该线程是会被中断被唤醒的,但是唤醒之后,会调用LockSupport.park再次进入等待状态,所以仅从宏观(表面)上面看ReentrantLock.lock是不支持响应中断的,从微观(原理)上面讲ReentrantLock.lock内部确实中断了响应,但是还是会被迫进行等待状态。
在明白了哪些方法会响应中断后,还需要明白响应中断方法的会不会清除中断标志位,对于Object.wait()、Thread.join、Thread.sleep方法都是会清除中断标志位了,这个上面已经说了,而对于LockSupport.park方法,是不会清除中断标志位的,不清除中断标志位意味着调用Thread.interrupt方法之后,碰到多个LockSupport.park方法线程不会进行等待状态了。
public class Test05 {
public static void main(String[] args) throws InterruptedException {
Thread thread = new Thread(() -> {
System.out.println("线程一正在执行,将进入等待状态,当前时间= "+System.currentTimeMillis()+", 此时的中断标志位:"+Thread.currentThread().isInterrupted());
LockSupport.park();
System.out.println("线程一从等待状态中醒来,当前时间= "+System.currentTimeMillis()+", 此时的中断标志位:" + Thread.currentThread().isInterrupted());
LockSupport.park();
System.out.println("线程一从等待状态中醒来,当前时间= "+System.currentTimeMillis()+", 此时的中断标志位:"+Thread.currentThread().isInterrupted());
});
System.out.println("主线程正在执行");
thread.start();
System.out.println("主线程等待,睡眠两秒");
TimeUnit.SECONDS.sleep(2);
thread.interrupt();
}
}
/*
执行结果:
主线程正在执行
主线程等待,睡眠两秒
线程一正在执行,将进入等待状态,当前时间 = 1592271902514, 此时的中断标志位:false
线程一从等待状态中醒来,当前时间 = 1592271904515, 此时的中断标志位:true
线程一从等待状态中醒来,当前时间 = 1592271904515, 此时的中断标志位:true
*/
可以看到当线程一调用 Thread.interrupt方法之后,会从线程会从等待状态中醒来,然后第二次调用LockSupport.park时,根本就不会再次进入等待状态,所以调用LockSupport.park是不会清除中断标志位的。
Object.wait()、Thread.join、Thread.sleep方法都是会清除中断标志位,这里就只演示Thread.sleep方法了,TimeUnit.SECONDS.sleep底层是调用Thread.sleep方法。
public class Test06 {
public static void main(String[] args) throws InterruptedException {
Thread thread = new Thread(() -> {
System.out.println("线程一正在执行,将进入等待状态,时间= "+System.currentTimeMillis()+",中断标志位:"+Thread.currentThread().isInterrupted());
try {
TimeUnit.SECONDS.sleep(5);
} catch (InterruptedException e) {
}
System.out.println("线程一从等待状态中醒来,时间= "+System.currentTimeMillis()+",中断标志位:"+Thread.currentThread().isInterrupted());
try {
TimeUnit.SECONDS.sleep(5);
} catch (InterruptedException e) {
}
System.out.println("线程一从等待状态中醒来,时间= "+System.currentTimeMillis()+",中断标志位:"+Thread.currentThread().isInterrupted());
});
System.out.println("主线程正在执行");
thread.start();
System.out.println("主线程等待,睡眠两秒");
TimeUnit.SECONDS.sleep(2);
thread.interrupt();
}
}
/*
执行结果:
主线程正在执行
主线程等待,睡眠两秒
线程一正在执行,将进入等待状态,时间= 1592272574294,中断标志位:false
线程一从等待状态中醒来,时间= 1592272576295,中断标志位:false
线程一从等待状态中醒来,时间= 1592272581295,中断标志位:false
*/
可以看到中断标志位都是false,而且线程一第二次调用 Thread.sleep时,还是会进行等待状态的。
synchronized测试中断:
public class Test03 {
//使用线程池进行测试
private static ExecutorService executorService = Executors.newFixedThreadPool(5);
public static void main(String[] args) throws InterruptedException {
Object luck = new Object();
executorService.execute(()->{
System.out.println("线程1打算获取锁");
synchronized (luck){
try {
System.out.println("线程1打算睡眠");
//sleep不会释放锁
TimeUnit.SECONDS.sleep(5);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("线程1已经执行完成");
}
});
//主线程睡眠1秒,保证线程1已经睡眠了
TimeUnit.SECONDS.sleep(1);
Thread thread = new Thread(() -> {
System.out.println("线程2打算获取锁");
//线程2会在此被阻塞,因为线程1已经拿到了锁,并抱着锁睡觉了
//在此中断线程是不会有任何响应的
synchronized (luck) {
try {
//判断当前线程的中断为
System.out.println("线程2的中断标志位 " + Thread.currentThread().isInterrupted());
//虽然在synchronized (luck) {中不会响应,但是线程的中断标志位还是true,所以执行到此,会响应迟到的中断
TimeUnit.SECONDS.sleep(1);
} catch (InterruptedException e) {
e.printStackTrace();
System.out.println("InterruptedException 线程2的中断标志位 " + Thread.currentThread().isInterrupted());
}
}
System.out.println("线程2已经执行完成");
});
thread.start();
//此时中断线程2,测试线程2在等待获取锁的时候会不会相应中断
thread.interrupt();
}
}
/*
执行结果:
线程1打算获取锁
线程1打算睡眠
线程2打算获取锁
线程1已经执行完成
线程2的中断标志位 true
java.lang.InterruptedException: sleep interrupted
at java.lang.Thread.sleep(Native Method)
at java.lang.Thread.sleep(Thread.java:340)
at java.util.concurrent.TimeUnit.sleep(TimeUnit.java:386)
at it.cast.basic.thread.interrupt.Test03.lambda$main$1(Test03.java:43)
at java.lang.Thread.run(Thread.java:748)
InterruptedException 线程2的中断标志位 false
线程2已经执行完成
*/
从上面例子可以得到,当线程等待进行synchronized的方法或是代码块,是不会响应中断的,如果此时调用Thread.interrupt方法是不会有任何方法,但是在阻塞的时候还是会讲中断标志位置位true的,那么在拿到锁之后,执行到TimeUnit.SECONDS.sleep(5);会迅速抛出一个中断的异常。
ReentrantLock测试中断:
public class Test04 {
private static ExecutorService executorService = Executors.newFixedThreadPool(5);
public static void main(String[] args) throws InterruptedException {
Lock lock = new ReentrantLock();
executorService.execute(()->{
System.out.println("线程1打算获取锁");
lock.lock();
try{
System.out.println("线程1打算睡眠");
//sleep不会释放锁
TimeUnit.SECONDS.sleep(5);
}catch (InterruptedException e){
}finally {
lock.unlock();
}
System.out.println("线程1已经执行完成");
});
//主线程睡眠1秒,保证线程1已经睡眠了
TimeUnit.SECONDS.sleep(1);
Thread thread = new Thread(() -> {
System.out.println("线程2打算获取锁");
//在此中断线程是不会有任何响应的
lock.lock();
try {
//判断当前线程的中断为
System.out.println("线程2的中断标志位 " + Thread.currentThread().isInterrupted());
//执行到此,会抛出中断异常,虽然这个中断操作是在lock.lock();进行的,但是会在
//执行sleep的时候响应,就是判断中断标志位
TimeUnit.SECONDS.sleep(5);
} catch (InterruptedException e) {
e.printStackTrace();
System.out.println("InterruptedException 线程2的中断标志位 " + Thread.currentThread().isInterrupted());
} finally {
lock.unlock();
}
System.out.println("线程1已经执行完成");
});
thread.start();
thread.interrupt();
}
}
/*
执行结果:
线程1打算获取锁
线程1打算睡眠
线程2打算获取锁
java.lang.InterruptedException: sleep interrupted
at java.lang.Thread.sleep(Native Method)
at java.lang.Thread.sleep(Thread.java:340)
at java.util.concurrent.TimeUnit.sleep(TimeUnit.java:386)
at it.cast.basic.thread.interrupt.Test04.lambda$main$1(Test04.java:47)
at java.lang.Thread.run(Thread.java:748)
线程1已经执行完成
线程2的中断标志位 true
InterruptedException 线程2的中断标志位 false
线程1已经执行完成
*/
分析和synchronized一样,在进行线程2进行lock方法内部之后是不会响应中断的。
参考文档:
理解java线程的中断(interrupt)
深入理解java线程
【多线程】——深入理解线程中断方式(interrupt)