文章目录
- 线程中断的作用
- 线程的等待状态
- WAITING
- TIMED_WAITING
- 线程从等待中恢复
- `java.lang.Thread`中断实现
- 相关方法
- 中断标识`interrupted`
- 一些小练习
- `Thread.interrupt()` 只唤醒线程并修改中断标识
- `sleep()` 清除中断状态标识
- Reference
线程中断的作用
线程中断可以使一个线程从等待状态变成就绪状态
使用线程中断,并不是要把线程给终止或是杀死,而是让线程不再继续等待,而是让线程不再继续等待,线程可以继续往下执行代码,线程发生中断后,会抛出一个中断的异常,决定如何处理就看业务代码怎么写
线程的等待状态
详细的JVM线程状态与OS线程状态相关可以看这篇,中断等待一般是从WAITING、TIME WAITING等阻塞,或者说挂起状态(感觉更准确,一般都是主动挂起,而BLOCK阻塞状态一般是竞争锁资源失败被动阻塞到队列中),恢复到RUNNABLE。
WAITING
Object.wait()
:使当前线程处于等待状态,直到另一个线程通过notify()
显式唤醒它;Thread.join()
:插队,当前线程需要等待join的线程执行完毕,底层调用的是Object实例的wait方法;LockSupport.park()
:除非获得调用许可,否则禁用当前线程进行线程调度。LockSupport.unpark()
, 恢复许可来唤醒
TIMED_WAITING
-
Thread.sleep(long millis)
:使当前线程睡眠指定时间 -
Object.wait(long timeout)
:线程休眠指定时间,需要与synchronized一起使用,等待期间可以通过notify()
/notifyAll()
唤醒;Object o = new Object(); synchronized (o){o.wait();//将当前线程挂起,并释放o锁o.notify();//唤醒一个等待在o锁等待队列的线程o.notifyAll();//唤醒等待在o锁等待队列的所有线程 }
-
Thread.join(long millis)
:等待当前线程最多执行millis毫秒,如果millis为0,则会一直执行;
上述三种方法使线程等待后,可以通过Interrupt()
显示唤醒(中断等待)
线程从等待中恢复
- 等待超时:
Thread.sleep(long millis)
等到时间了自己回复 - 得到通知:
Object.wait()
线程休眠期间,其他线程可通过notify()
/notifyAll()
将其唤醒; - 使用中断:
Interrupt()
java.lang.Thread
中断实现
相关方法
-
**
java.lang.Thread.interrupt()
:**中断目标线程,给目标线程发一个中断信号,线程被打上中断标记(设为true)。public void interrupt() {if (this != Thread.currentThread()) {checkAccess();// thread may be blocked in an I/O operationsynchronized (interruptLock) {Interruptible b = nioBlocker;if (b != null) {interrupted = true;interrupt0(); // inform VM of interruptb.interrupt(this);return;}}}// 打上中断标记interrupted = true;// interrupt0()是一个native方法,主要就是用cpp从OS层面将线程唤醒interrupt0(); // inform VM of interrupt }
-
**
java.lang.Thread.isInterrupted()
:**判断目标线程是否被中断,不会清除中断标记。public boolean isInterrupted() {return interrupted; }
-
**
java.lang.Thread.interrupted()
:**判断目标线程是否被中断,会清除中断标记。public static boolean interrupted() {return currentThread().getAndClearInterrupt(); }// ... boolean getAndClearInterrupt() {boolean oldValue = interrupted;// 因为读取interrupted字段时也可能被中断// 此时如果直接清除interrupted,会导致这次新的中断丢失// 所以这里要判断一下if (oldValue) {interrupted = false;clearInterruptEvent(); // native方法}return oldValue; }
中断标识interrupted
Thread中有一个中断标识属性volatile boolean interrupted
,用于标记当前线程是否中断。有两点要注意:
Thread.interrupt()
方法做两个工作:- 在当前线程将中断标志修改为true,并不是停止线程
- 通过native的
interrupt0()
将等待的线程唤醒
- 如果当前线程在调用Object类的
wait()
方法或者这个类的join()
或sleep()
方法被打断时,会将它的中断状态将被清除(interrupted
设为false),并且它会收到一个InterruptedException
异常。
当线程正在等待、休眠或以其他方式被占用,并且线程在活动之前或期间被中断抛出,都会抛出
InterruptedException
-
(个人理解)抛出
InterruptedException
,这个行为是中断的直接结果,捕获这个异常后我们可以让线程执行唤醒后的逻辑(继续运行或者return结束)try{Thread.sleep(3000); } catch (InterruptedException e) {// 打印日志或者return退出都可以// ... } // 当前线程的后续逻辑 // ...
-
线程通过抛出IE从非活跃状态(WAITING、TIMED WAITING)恢复到活跃状态(RUNNABLE)。如果线程本身就是活跃状态,则会无视RUNNABLE,也不会抛出IE。此时如果想处理中断,就需要**
isInterrupted()
,interrupted()
**来获取中断状态标识interrupted
一些小练习
Thread.interrupt()
只唤醒线程并修改中断标识
sleep、wait、join挂起线程都会修改中断标识符并抛出InterruptedException
,如果没有抛出IE的情况下(比如yield
后线程还是RUNNABLE状态)希望响应中断,则需要isInterrupted()
,interrupted()
来获取中断状态标识interrupted
private static void test1(){// 程序中没有响应中断信号的逻辑,线程不会被中断Thread thread = new Thread(()->{System.out.println("线程启动");while (true){Thread.yield();}});thread.start();thread.interrupt();
}
private static void test2(){// 手动响应中断,退出线程Thread thread = new Thread(()->{System.out.println(Thread.currentThread().getName()+": 线程启动");while (true){Thread.yield();// 响应中断if (Thread.currentThread().isInterrupted()){System.out.println(Thread.currentThread().getName()+": 线程被中断");return;}}});thread.start();thread.interrupt();
}
sleep()
清除中断状态标识
private static void test3() throws InterruptedException {// 中断失败,因为sleep会将当前线程的interrupted清楚(设为false)Thread thread = new Thread(()->{System.out.println(Thread.currentThread().getName()+": 线程启动");while (true){// 响应中断if (Thread.currentThread().isInterrupted()){System.out.println(Thread.currentThread().getName()+": 线程被中断,程序退出");return;}try{Thread.sleep(3000);} catch (InterruptedException e) {System.out.println(Thread.currentThread().getName()+": 线程休眠被中断");}}});thread.start();Thread.sleep(2000);thread.interrupt();
}
- 最开始,子线程start,
Thread.currentThread().isInterrupted()
为false,不能进入if(Thread.currentThread().isInterrupted())
直接进入try里的sleep(3s) - 主线程sleep(2s)
- 2s后,主线程自己到点唤醒,通过
thread.interrupt()
中断唤醒子线程,此时中断标识interrupted
为true,由于sleep()
抛出InterruptedException
(同时将中断标识清除,设为false)被catch捕获,打印“线程休眠被中断” - 回到
if (Thread.currentThread().isInterrupted())
,由于前面sleep抛出IE的同时将中断标识设为false,所以不能执行这个if里的逻辑,结果就是一直在while里重复sleep
private static void test4() throws InterruptedException {Thread thread = new Thread(() -> {System.out.println(Thread.currentThread().getName()+": 线程启动");while (true){// 响应中断if (Thread.currentThread().isInterrupted()){System.out.println(Thread.currentThread().getName()+": 线程被中断,程序退出");return;}try{Thread.sleep(3000);} catch (InterruptedException e) {System.out.println(Thread.currentThread().getName()+": 线程休眠被中断");Thread.currentThread().interrupt();}}});thread.start();Thread.sleep(2000);thread.interrupt();
}
这里再catch到sleep的IE之后再通过Thread.currentThread().interrupt()
把当前线程的interrupted
表示符置为true,下一个循环就能执行if (Thread.currentThread().isInterrupted())
的return逻辑了。
为什么呢?
public static void main(String[] args) {System.out.println(Thread.currentThread().isInterrupted());Thread.currentThread().interrupt();try {System.out.println(LocalTime.now() +": 开始睡眠");System.out.println(Thread.currentThread().isInterrupted());Thread.sleep(3000);} catch (InterruptedException e) {System.out.println(Thread.currentThread().isInterrupted());System.out.println(LocalTime.now()+": 发生中断");}System.out.println(LocalTime.now()+": 结束睡眠");
}
打印一下中断标识,可以看出sleep之前interrupt()
已经将interrupted
设为true,sleep会将其改为false并直接抛出IE
Reference
【Java并发·08】线程中断 interrupt
对于Java线程中断的理解,哪种情况下会响应中断?哪种情况下不响应中断?
Java 线程中断?看这篇就够了