Thread 对象 native 实现中有一个标识位属性代表线程的中断状态,可以认为它是一个 Boolean 类型的变量,初始值为 false
通过调用一个线程的isInterrupted()方法可以判断该线程是否处于中断状态
通过调用一个线程的interrupt()方法将该线程中断,该操作只是将该线程的中断标志置为 true,至于线程以何种动作处理该中断就要看线程自己
如果线程因sleep(), wait(), join()方法处于等待状态,那么线程会定时检查中断标志是否为 true,如果为 true 则会在调用方法处抛出 InterruptedException 异常并清除中断标志重新设置为 false。抛出异常是为了唤醒线程
如果线程正在运行、竞争锁,那么是不可中断的,直接忽略
public static void main(String[] args) throws InterruptedException { Thread mainThread = Thread.currentThread(); Thread sleepThread = new Thread(() -> { try { Thread.sleep(10000); } catch (InterruptedException e) { throw new RuntimeException(e); } }, "SleepThread"); Thread waitThread = new Thread(() -> { try { Thread.currentThread().wait(); } catch (InterruptedException e) { throw new RuntimeException(e); } }, "WaitThread"); Thread joinThread = new Thread(() -> { try { mainThread.join(); } catch (InterruptedException e) { throw new RuntimeException(e); } }, "JoinThread"); sleepThread.start(); waitThread.start(); joinThread.start(); System.out.println("中断前标志位 ..."); System.out.println("Sleep Thread isInterrupted: " + sleepThread.isInterrupted()); System.out.println("Wait Thread isInterrupted: " + waitThread.isInterrupted()); System.out.println("Join Thread isInterrupted: " + joinThread.isInterrupted()); // 中断 sleepThread.interrupt(); waitThread.interrupt(); joinThread.interrupt(); Thread.sleep(2000); System.out.println("中断后标志位 ..."); System.out.println("Sleep Thread isInterrupted: " + sleepThread.isInterrupted()); System.out.println("Wait Thread isInterrupted: " + waitThread.isInterrupted()); System.out.println("Join Thread isInterrupted: " + joinThread.isInterrupted());}// result中断前标志位 ...Sleep Thread isInterrupted: falseWait Thread isInterrupted: falseJoin Thread isInterrupted: false中断后标志位 ...Sleep Thread isInterrupted: falseWait Thread isInterrupted: falseJoin Thread isInterrupted: false当我们调用Object.wait(), Object.join(), LockSupport.park()方法时,会使运行状态的线程变为等待状态
当我们调用Object.notify(), Object.notifyAll(), LockSupport.unpark()方法时,会使等待状态的线程变为运行状态
public static void main(String[] args) { Thread consume = new Thread(() -> { for (int i = 0; i < 10; i++) { LockSupport.park(); // 等待 System.out.println("消费者线程: " + i); } }); Thread product = new Thread(() -> { for (int i = 0; i < 10; i++) { try { Thread.sleep(1000); } catch (InterruptedException e) { throw new RuntimeException(e); } LockSupport.unpark(consume); // 唤醒 } }); consume.start(); product.start();}// result (每隔 1s 输出 1 次)消费者线程: 0消费者线程: 1消费者线程: 2消费者线程: 3消费者线程: 4消费者线程: 5消费者线程: 6消费者线程: 7消费者线程: 8消费者线程: 9前面说过,中断可以唤醒处于等待状态的线程,而调用LockSupport.park()正好可以使线程处于等待状态,那么是否可以使用interrupt()和LockSupport.park()实现无限次的唤醒和等待呢?
public static void main(String[] args) { Thread consume = new Thread(() -> { for (int i = 0; i < 10; i++) { LockSupport.park(); // 等待 System.out.println("消费者线程: " + i); } }); Thread product = new Thread(() -> { consume.interrupt(); // 中断 }); consume.start(); product.start();}// result消费者线程: 0消费者线程: 1消费者线程: 2消费者线程: 3消费者线程: 4消费者线程: 5消费者线程: 6消费者线程: 7消费者线程: 8消费者线程: 9现在就可以看出很明显的问题,我们只中断了一次,为什么会输出 10 个呢??不应该只输出 1 个就继续等待了吗?
我们先来仔细介绍一下park()/unpark()通信机制,它们俩之间是通过「许可 (permit)」来通信,是一个整型变量,它的数量是不允许叠加的,只有 0 和 1 两种状态,0 表示没有许可,1 表示有许可
调用unpark()会将许可 +1,如果已经是 1 就不变;调用 park() 会将许可 -1,如果已经是 0 就不变,但是会等待新的许可来才能被唤醒
它们俩有一个独特的性质:不需要管调用的先后顺序,可以先调用unpark()获得一个许可,后面再调用park()使用掉这个许可
注意:如果先调用unpart(),必须保证线程已经启动,也就是执行了start()方法,否则没有效果。详情可见 JDK 注释:
This operation is not guaranteed to have any effect at all if the given thread has not been started.
park()是调用UNSAFE.park(false, 0L)方法,而UNSAFE.park(false, 0L)是一个本地方法,在 os_posix.cpp 中。代码太长,看不懂???直接上简化版的伪代码:
park() { if (permit > 0) { permit = 0; return; }
if (中断状态 == true) { return; }
阻塞当前线程; // 将来会从这里被唤醒
if (permit > 0) { permit = 0; }}当 permit = 0 时表示线程处于等待状态,当 permit = 1 时表示线程处于唤醒状态,所以park()/unpark()就是 permit 处于 0 和 1 之间的变化
可以看出只要「permit = 1」或者「中断状态 = true」,那么执行park()就不能够阻塞线程,这也解释了为什么上面只调用了一次中断,后续就不会因park()而等待
同理unpark()简化版的伪代码如下:
unpark(Thread thread) { if (permit < 1) { permit = 1; if (thread处于阻塞状态) 唤醒线程thread; }}简化版的伪代码如下:
interrupt() { if (中断状态 == false) { 中断状态 = true; } unpark(this); // 注意这是 Thread 的成员方法,所以可以通过 this 获得 Thread 对象}interrupt()会设置中断状态为 true,同时调用一次unpark(),将 permit 设置为 1
简化版的伪代码如下:
sleep(){ //这里忽略了休眠时间,假设时间是大于 0 即可 if (中断状态 == true) { 中断状态 = false; throw new InterruptedException(); } 线程开始休眠;
if (中断状态 == true) { 中断状态 = false; throw new InterruptedException(); }}sleep()会去检测中断状态,如果中断状态为 true,就消耗掉它 (置为 false) 并抛出异常
注意:wait(), join()同sleep()