前言: 在 Java 多线程中, 中断一直围绕着我们, 当我们阅读各种关于 Java 多线程的资料, 书籍时,"中断" 一词总是会出现, 笔者对其的理解也是朦朦胧胧, 因此非常有必要搞清楚 Java 多线程的中断机制.
1.Java 中断机制是什么
Java 中断机制是一种协作机制, 也就是说通过中断并不能直接终止另一个线程, 而需要被中断的线程自己处理中断. 这好比老师要求学生要高质量完成作业, 但是学生是否高质量完成作业, 完全取决于学生自己.
Java 中断模型非常的简单: 每个线程对象里都有一个 boolean 类型的标识(当然 jdk 源码中是看不到该标识位, 它位于虚拟机层面), 代表着是否有中断请求(该请求可以来自所有线程, 包括被中断的线程本身). 例如, 当线程 t1 想中断线程 t2, 只需要在线程 t1 中将线程 t2 对象的中断标识置为 true, 然后线程 t2 可以选择在合适的时候处理该中断请求, 甚至可以不理会该请求, 就像这个线程没有被中断一样.
综合上述两段文字的描述, 对 Java 中断机制进行总结: Java 中断是一种机制, 并不是真的停止线程, 而是对线程对象打上一个中断标记, 具体如何处理还是要看被中断线程如何操作.
2.Thread 类提供的中断相关方法
中断线程, 注意该方法未被 static 修饰, 因此该方法被 Thread 对象调用. 并且该方法仅仅是为线程打一个中断的标记, 将线程中断状态设置为 true.
测试 Thread 对象是否中断, 主要该方法也未被 static 修饰, 因此该方法也应该被 Thread 对象调用, 如果线程被打了中断标记, 返回 true, 否则返回 false. 特别注意该方法不影响中断状态, 这里主要和 interrupted()方法做对比.
测试当前线程是否中断, 注意该方法被 static 修饰, 并且该方法会清除线程的中断标记, 将中断标记设置为 false. 也就是说, 如果连续两次调用该方法, 则第二次调用将返回 false(在第一次调用已清除了其中断状态之后, 且第二次调用检验完中断状态前, 当前线程再次中断的情况除外).
总结:
- #1. 真正中断线程的方法为 interrupt(), 并且该方法也仅仅是为 Thread 对象打一个中断的标记, 而不是立即终止线程.
- #2.isInterrupted()方法未被 static 修饰, 测试 Thread 对象是否中断, 也就是判断线程对象是否有中断标记. 该方法不会清除中断标记.
- #3.interrupted()方法被 static 修饰, 测试当前线程是否中断, 注意该方法会清除线程中断的标记.
以上三个函数的源码逻辑简单, 主要调用了 native 方法, 这里不进行阐述.
3. 中断处理时机
中断作为一种协作机制, 不会强求被中断线程一定要在某个点进行处理. 实际上, 被中断线程只需在合适的时候处理即可, 如果没有合适的时间点, 甚至可以不处理, 这时候在任务处理层面, 就跟没有调用中断方法一样."合适的时候" 与线程正在处理的业务逻辑紧密相关.
处理时机决定着程序的效率与中断响应的灵敏性, 频繁的检查中断状态可能会使程序执行效率下降, 相反, 检查的较少可能使中断请求得不到及时响应. 如果发出中断请求之后, 被中断的线程继续执行一段时间不会给系统带来灾难, 那么就可以将中断处理放到方便检查中断, 同时又能从一定程度上保证响应灵敏度的地方. 当程序的性能指标比较关键时, 可能需要建立一个测试模型来分析最佳的中断检测点, 以平衡性能和响应灵敏性.
4. 线程中断举例
停不下来的线程
- public static void main(String[] args) throws InterruptedException {
- Thread t1 = new Thread(() -> {
- for (int i = 0; i <500000; i++) {
- System.out.println("i=" + (i + 1));
- }
- System.out.println("我是 t1 线程");
- });
- t1.start();
- Thread.sleep(200);
- t1.interrupt();
- }
上述代码运行结果如下:
从运行结果来看, 线程并未终止成功, 这也符合 interrupt()函数的功能描述, 仅仅是为线程打一个中断标记, 具体怎么处理还要看线程自己如何操作.
将上面代码做如下修改:
- public static void main(String[] args) throws InterruptedException {
- Thread t1 = new Thread(() -> {
- for (int i = 0; i <500000; i++) {
- if (Thread.currentThread().isInterrupted()) { // 对中断做处理
- System.out.println("t1 线程被中断了");
- return;
- }
- System.out.println("i=" + (i + 1));
- }
- System.out.println("我是 t1 线程");
- });
- t1.start();
- Thread.sleep(200);
- t1.interrupt();
- }
其运行结果如下:
上述代码对中断进行了处理, 所以循环并未走完, t1 线程被成功中断.
interrupted()和 isInterrupted()的区别
- public static void main(String[] args) throws InterruptedException {
- Thread t1 = new Thread(() -> {
- for (int i = 0; i <500000; i++) {
- System.out.println("i=" + (i + 1));
- }
- System.out.println("我是 t1 线程");
- });
- t1.start();
- Thread.sleep(200);
- t1.interrupt();
- System.out.println("isInterrupted()=" + t1.isInterrupted());
- System.out.println("isInterrupted()=" + t1.isInterrupted());
- System.out.println("interrupted()=" + t1.interrupted());
- System.out.println("interrupted()=" + Thread.interrupted());
- }
运行结果如下:
为什么会出现上面的运行结果呢, 从源码上最容易理解:
该方法未被 static 修饰 [isInterrupted(false) 表示不会清除中断标志, isInterrupted 为 native 方法] , 所以该方法被 Thread 对象调用, 返回 Thread 对象的中断状态.
该方法被 static 修饰 [注意 isInterrupted(true) 表示会清除中断标志] , 该方法返回当前线程的中断状态, 在上述代码中, 当前线程为 main 方法代表的主线程, 并没有进行中断操作, 所以打印结果为 false.
修改上述代码:
- public static void main(String[] args) throws InterruptedException {
- Thread t1 = new Thread(() -> {
- for (int i = 0; i < 500000; i++) {
- System.out.println("i=" + (i + 1));
- }
- System.out.println("我是 t1 线程");
- });
- t1.start();
- Thread.sleep(200);
- t1.interrupt();
- Thread.currentThread().interrupt();
- System.out.println("isInterrupted()=" + t1.isInterrupted());
- System.out.println("isInterrupted()=" + t1.isInterrupted());
- System.out.println("interrupted()=" + t1.interrupted());
- System.out.println("interrupted()=" + Thread.interrupted());
- }
注意第 11 行代码, 其运行结果如下:
从结果充分说明连续两次调用 interrupted()会清除中断标记.
总结
通过上述的分析, 对 Java 的中断机制的核心要点做如下总结:
Java 中断机制是一种协作机制, 中断只是给线程打一个中断标记, 具体如何操作还要看线程自己, by myself.
interrupt()函数作用仅仅是为线程打一个中断标记.
interrupted()与 isInterrupted()函数, 都是返回线程的中断状态, 但是 interrupted()被 static 修饰, 返回当前线程的中断状态, 并且会清除线程的中断标记; 而 isInterrupted()未被 static 修饰, 被 Thread 对象调用, 它不会清除线程的中断标记.
by Shawn Chen,2019.02.17, 上午.
来源: http://www.bubuko.com/infodetail-2957445.html