介绍

Java的中断是一种协作机制,也就是说通过中断并不能直接中断另外一个线程,而需要被中断的线程自己处理中断。
在Java的中断模型中,每个线程都有一个boolean标识,代表着是否有中断请求(该请求可以来自所有线程,包括被中断的线程本身)。例如,当线程t1想中断线程t2,只需要在线程t1中将线程t2对象的中断标识置为true,然后线程2可以选择在合适的时候处理该中断请求,甚至可以不理会该请求,就像这个线程没有被中断一样。

中断方法

线程中断相关的方法主要是下面的3方法,他们具体的作用见下面的表格:

方法名 介绍
void interrupt() 中断线程,设置线程的中断位true
boolean isInterrupted() 检查线程的中断标记位,true-中断状态, false-非中断状态
static boolean interrupted() 静态方法,返回当前线程的中断标记位,同时清除中断标记,改为false。比如当前线程已中断,调用interrupted(),返回true, 同时将当前线程的中断标记位改为false, 再次调用interrupted(),会发现返回false

记忆方法推荐:

  • interrupt()是一个动词,表示中断线程。
  • Interrupted是一个形容词,用于检查线程的中断位,而isInterrupted()方法只是简单的检查,interrupted()处理的更加复杂。

可中断的阻塞

针对线程处于由sleep, wait, joinLockSupport.park等方法调用产生的阻塞状态时,调用interrupt方法,会抛出异常InterruptedException,同时会清除中断标记位,自动改为false。

不可中断的阻塞

  1. java.io包中的同步Socket I/O
  2. java.io包中的同步I/O
  3. Selector的异步I/O
  4. sychronized加的锁

实践案例

中断sleep、wait、join等方法

  1. private static void test1() throws InterruptedException {
  2. Thread t1 = new Thread(() -> {
  3. try {
  4. Thread.sleep(1000);
  5. } catch (InterruptedException e) {
  6. log.error("error", e);
  7. }
  8. }, "t1");
  9. t1.start();
  10. Thread.sleep(500);
  11. t1.interrupt();
  12. Thread.sleep(100);
  13. log.info(" interrupt status : {}", t1.isInterrupted());
  14. }

image.png
结论:阻塞方法sleep响应中断,抛出InterruptedException,同时清除中断标记位为false。

中断LockSupport.park方法

  1. public static void test3() throws InterruptedException {
  2. Thread t3 = new Thread(() -> {
  3. log.debug("t3 park.....");
  4. LockSupport.park();
  5. log.debug("t3 unpark.....");
  6. log.debug("interrupt status: [{}]", Thread.currentThread().isInterrupted());
  7. log.debug("t3 第二次 park.....");
  8. LockSupport.park();
  9. log.debug("t3 中断位为true, park失效.....");
  10. }, "t3");
  11. t3.start();
  12. Thread.sleep(1000);
  13. t3.interrupt();
  14. }

image.png
结论:阻塞方法park响应中断, 不会抛出异常,同时不会清除中断标记位,任为true。

中断正常方法

  1. private static void test2() throws InterruptedException {
  2. Thread t2 = new Thread(() -> {
  3. while (true) {
  4. boolean isInterrupted = Thread.currentThread().isInterrupted();
  5. if (isInterrupted) {
  6. log.info("interrupt status: {}", isInterrupted);
  7. break;
  8. }
  9. }
  10. }, "t2");
  11. t2.start();
  12. Thread.sleep(500);
  13. t2.interrupt();
  14. Thread.sleep(100);
  15. log.info(" thread status, {}, interrupt status : {}", t2.getState(), t2.isInterrupted());
  16. }

image.png
结论:打断正常运行的线程, 不会清空打断状态,同时线程结束后,重置中断状态位。

常见模式

这里提供针对interrupt中断使用的一种常见模式,方便大家在后续的开发过程中使用。

两阶段中止模式

使用场景

在一个线程中“优雅”地停止另一个线程的工作,在停止的时候给另一个线程”料理后事”的机会。比如有个监控线程在做监控,在主线程可以终止他的工作。

代码展示

  1. class MonitorTask {
  2. private Thread thread;
  3. // 开始监控
  4. public void start() {
  5. thread = new Thread(() -> {
  6. while (true) {
  7. Thread curr = thread.currentThread();
  8. // 如果当前线程是中断状态
  9. if(curr.isInterrupted()) {
  10. log.debug("清理资源,料理后事");
  11. break;
  12. }
  13. try {
  14. // 监控执行工作...
  15. log.debug("监控执行ing");
  16. Thread.sleep(1000);
  17. } catch (InterruptedException e) {
  18. log.error("error", e);
  19. // 此处中断位被清除,变为false, 重新改为true,进入循环,处理线程后事
  20. curr.interrupt();
  21. }
  22. }
  23. }, "监控线程");
  24. thread.start();
  25. }
  26. // 终止线程
  27. public void stop() {
  28. thread.interrupt();
  29. }
  30. }
  1. MonitorTask monitorTask = new MonitorTask();
  2. monitorTask.start();
  3. Thread.sleep(1500);
  4. monitorTask.stop();

image.png