一、概述

  之前讲解Thread类中方法的时候,interrupt()、interrupted()、isInterrupted()三个方法没有讲得很清楚,只是提了一下。现在把这三个方法同一放到这里来讲,因为这三个方法都涉及到多线程的一个知识点----中断机制。

  Java没有提供一种安全、直接的方法来停止某个线程,而是提供了中断机制。中断机制是一种协作机制,也就是说通过中断并不能直接终止另一个线程,而需要被中断的线程自己处理。有个例子举个蛮好,就像父母叮嘱出门在外的子女要注意身体一样,父母说了,但是子女是否注意身体、如何注意身体,还是要看自己。

  中断机制也是一样的,每个线程对象里都有一个标识位表示是否有中断请求(当然JDK的源码是看不到这个标识位的,是虚拟机线程实现层面的),代表着是否有中断请求。

二、三个中断有关的方法

  1、interrupt()方法

 /**
* Interrupts this thread.
*
* <p> Unless the current thread is interrupting itself, which is
* always permitted, the {@link #checkAccess() checkAccess} method
* of this thread is invoked, which may cause a {@link
* SecurityException} to be thrown.
*
* <p> If this thread is blocked in an invocation of the {@link
* Object#wait() wait()}, {@link Object#wait(long) wait(long)}, or {@link
* Object#wait(long, int) wait(long, int)} methods of the {@link Object}
* class, or of the {@link #join()}, {@link #join(long)}, {@link
* #join(long, int)}, {@link #sleep(long)}, or {@link #sleep(long, int)},
* methods of this class, then its interrupt status will be cleared and it
* will receive an {@link InterruptedException}.
*
* <p> If this thread is blocked in an I/O operation upon an {@link
* java.nio.channels.InterruptibleChannel InterruptibleChannel}
* then the channel will be closed, the thread's interrupt
* status will be set, and the thread will receive a {@link
* java.nio.channels.ClosedByInterruptException}.
*
* <p> If this thread is blocked in a {@link java.nio.channels.Selector}
* then the thread's interrupt status will be set and it will return
* immediately from the selection operation, possibly with a non-zero
* value, just as if the selector's {@link
* java.nio.channels.Selector#wakeup wakeup} method were invoked.
*
* <p> If none of the previous conditions hold then this thread's interrupt
* status will be set. </p>
*
* <p> Interrupting a thread that is not alive need not have any effect.
*
* @throws SecurityException
* if the current thread cannot modify this thread
*
* @revised 6.0
* @spec JSR-51
*/
public void interrupt() {
if (this != Thread.currentThread())
checkAccess(); synchronized (blockerLock) {
Interruptible b = blocker;
if (b != null) {
interrupt0(); // Just to set the interrupt flag
b.interrupt(this);
return;
}
}
interrupt0();
}
 /* Some private helper methods */
private native void setPriority0(int newPriority);
private native void stop0(Object o);
private native void suspend0();
private native void resume0();
private native void interrupt0();
private native void setNativeName(String name);

  ①:看一下第47行,interrupt()方法的作用只是设置中断标识位(Just to set the interrupt flag),并不会强制要求线程必须进行处理。再看一下第32行,interrupt()方法作用的线程是处于不是终止状态或新建状态的线程(Interrupting a thread that is not alive need not have any effect.)。

  关于线程是否是alive的判断(A thread is alive if it has been started and has not yet died.)

  ②:看一下第29-30行,只有当那三种情况都不成立时,interrupt()方法才会设置线程的中断标识位(If none of the previous conditions hold then this thread's interrupt status will be set.)。这里介绍第一种,当调用Object的wait()/wait(long)/wait(long, int)方法,或是调用线程的join()/join(long)/join(long, int)/sleep(long)/sleep(long, int)方法, 那么interrupt()方法作用的线程的中断标识位会被清除并抛出InterruptedException异常。其余两种情况自己参看注释。

  ③:看一下第二块代码的第6行interrupt0()方法(设置中断标识位),这是一个被native修饰的方法,很明显这是一个本地方法,是Java虚拟机实现的。

  2、isInterrupted()

  该方法的作用就是测试线程是否已经中断,且线程的中断标识位并不受该方法的影响。

 /**
* Tests whether this thread has been interrupted. The <i>interrupted
* status</i> of the thread is unaffected by this method.
*
* <p>A thread interruption ignored because a thread was not alive
* at the time of the interrupt will be reflected by this method
* returning false.
*
* @return <code>true</code> if this thread has been interrupted;
* <code>false</code> otherwise.
* @see #interrupted()
* @revised 6.0
*/
public boolean isInterrupted() {
return isInterrupted(false);
} /**
* Tests if some Thread has been interrupted. The interrupted state
* is reset or not based on the value of ClearInterrupted that is
* passed.
*/
private native boolean isInterrupted(boolean ClearInterrupted);

  ①:注意一下第5-7行,若是调用isInterrupted()方法时,当前已经调用interrupt()方法的线程was not alive,方法会返回false,而不是true。

  ②:最终调用的是isInterrupted(boolean ClearInterrupted),这个方法是一个native的,看得出也是Java虚拟机实现的。方法的参数ClearInterrupted,顾名思义,清除中断标识位,这里传递false,明显就是不清除。

  举例:验证一下①中情况

public class Thread01 extends Thread{

    @Override
public void run() { }
}

  测试一下:

public class Test {
public static void main(String[] args) throws InterruptedException {
Thread01 thread01 = new Thread01();
thread01.start();
thread01.interrupt();
Thread.sleep(10);
System.out.println(thread01.isInterrupted());
System.out.println(thread01.isInterrupted());
System.out.println(thread01.isInterrupted());
}
}

  结果:

false
false
false

  可以看到,虽然调用了interrupt()方法,但是在调用isInterrupted()方法的时候,线程已经执行完了,所以返回的是false。

  看一下反例,调用interrupt()方法,在线程没执行完的时候调用isInterrupted()方法

public class Thread01 extends Thread{

    @Override
public void run() {
for(int i = 0; i < 50000; i++) { }
}
}

  测试一下:

public class Test {
public static void main(String[] args) throws InterruptedException {
Thread01 thread01 = new Thread01();
thread01.start();
thread01.interrupt();
System.out.println(thread01.isInterrupted());
System.out.println(thread01.isInterrupted());
System.out.println(thread01.isInterrupted());
}
}

  结果:

true
true
true

  3、interrupted()

  该方法的作用就是测试当前线程(currentThread)是否已经中断,且线程的中断标识位会被清除,换句话说,连续成功调用两次该方法,第二次的返回值一定是false。注意该方法是一个静态方法。

 /**
* Tests whether the current thread has been interrupted. The
* <i>interrupted status</i> of the thread is cleared by this method. In
* other words, if this method were to be called twice in succession, the
* second call would return false (unless the current thread were
* interrupted again, after the first call had cleared its interrupted
* status and before the second call had examined it).
*
* <p>A thread interruption ignored because a thread was not alive
* at the time of the interrupt will be reflected by this method
* returning false.
*
* @return <code>true</code> if the current thread has been interrupted;
* <code>false</code> otherwise.
* @see #isInterrupted()
* @revised 6.0
*/
public static boolean interrupted() {
return currentThread().isInterrupted(true);
}
 private native boolean isInterrupted(boolean ClearInterrupted);

  ①:注意一下第9-11行,若是调用interrupted()方法时,当前已经调用interrupt()方法的线程was not alive,方法会返回false,而不是true。

  ②:最终调用的是isInterrupted(boolean ClearInterrupted),这个方法是一个native的,看得出也是Java虚拟机实现的。方法的参数ClearInterrupted,顾名思义,清除中断标识位,这里传递true,明显就是清除线程的中断标识位。

  此外,JDK API中有些类的方法也可能会调用中断,比如FutureTask的cancel,如果传入true则会在正在运行的异步任务上调用interrupt()方法,又如ThreadPoolExecutor中的shutdownNow方法会遍历线程池中的工作线程并调用线程的interrupt()方法。这些场景下只要代码没有对中断作出响应,那么任务将一直执行下去。

  举例1:说明interrupted()方法测试的是当前线程是否被中断

public class Thread01 extends Thread{

    private List<Integer> list = new ArrayList<>();
@Override
public void run() {
System.out.println("开始执行run方法");
for(int i = 0; i < 50000; i++) {
list.add(i);
}
System.out.println("执行run方法结束"); }
}

  测试:

 public class Test {
public static void main(String[] args) throws InterruptedException {
Thread01 thread01 = new Thread01();
thread01.start();
Thread.sleep(5);
thread01.interrupt();
System.out.println(Thread.interrupted());
System.out.println(thread01.isInterrupted());
System.out.println(thread01.isInterrupted());
}
}

  结果:

开始执行run方法
false
true
true
执行run方法结束

  说明:由8行和9行结果来看,thread01确实是在执行期间被设置了中断标识位,但是第7行结果并没有返回true,这是因为Thread.interrupted()方法测试的当前线程是否处于中断状态,其当前线程是main线程,而main线程并没有处于中断状态,所以返回false。

  举例2:若是run方法之行结束了,再来测试thread01是否处于中断状态,那么结果是什么

public class Thread01 extends Thread{
@Override
public void run() {
System.out.println("开始执行run方法");
System.out.println("执行run方法结束");
}
}

  测试:sleep方法确保thread01执行完了在测试其是否处于中断状态

public class Test {
public static void main(String[] args) throws InterruptedException {
Thread01 thread01 = new Thread01();
thread01.start();
thread01.interrupt();
Thread.sleep(50);
System.out.println(Thread.interrupted());
System.out.println(thread01.isInterrupted());
System.out.println(thread01.isInterrupted());
}
}

  结果:

开始执行run方法
执行run方法结束
false
false
false

  说明:可以看到,即使thread01被设置了中断标识位,但若是线程执行完成了再来测试其是否处于中断状态,那么一定会返回false。

  举例3:中断main线程,给main线程设置中断标识位

public class Thread01 extends Thread{

    private List<Integer> list = new ArrayList<>();
@Override
public void run() {
System.out.println("开始执行run方法");
for(int i = 0; i < 50000; i++) {
list.add(i);
}
System.out.println("执行run方法结束");
}
}

  测试:

public class Test {
public static void main(String[] args) throws InterruptedException {
Thread01 thread01 = new Thread01();
thread01.start();
thread01.interrupt();
Thread.sleep(5);
Thread.currentThread().interrupt();
System.out.println(Thread.interrupted());
System.out.println(Thread.interrupted());
System.out.println(thread01.isInterrupted());
System.out.println(thread01.isInterrupted());
}
}

  结果:

开始执行run方法
true
false
true
true
执行run方法结束

三、中断处理时机

  这其实是一个很宽泛的、没有标注答案的话题。显然,作为一种协作机制,不会强求被中断的线程一定要在某个点进行中断处理。实际上,被中断线程只需要在合适的时候处理即可,如果没有合适的时间点,甚至可以不处理。"合适的时间点"就和业务逻辑密切相关了。

  处理时机决定着程序的效率和响应的灵敏度。频繁的检查中断可能会导致程序执行效率低下,较少的检查则可能导致中断请求得不到及时响应。在实际场景中,如果性能指标比较关键,可能需要建立一个测试模型来分析最佳的中断检测点,以平衡性能和响应灵敏性

四、线程中断举例

  示例1:线程被设置了中断标识位且没有抛出异常:InterruptedException

public class Thread01 extends Thread{

    @Override
public void run() { while(true){
if(Thread.currentThread().isInterrupted()){
System.out.println(Thread.currentThread().getName() + "被中断了");
break;
}else{
System.out.println(Thread.currentThread().getName() + "在运行中");
}
} }
}

  测试:

public class Test {
public static void main(String[] args) throws InterruptedException {
Thread01 thread01 = new Thread01();
thread01.start();
Thread.sleep(1000);
thread01.interrupt();
}
}

  结果:

.......
Thread-0在运行中
Thread-0在运行中
Thread-0在运行中
Thread-0在运行中
Thread-0在运行中
Thread-0在运行中
Thread-0在运行中
Thread-0被中断了

  代码分为以下几步:

  1、main函数起一个thread01线程

  2、main函数1秒钟之后给t线程打一个中断标识位,表示thread01线程要中断

  3、thread01线程无限轮询自己的中断标识位,中断了则打印、退出,否则一直运行

  从控制台上打印的语句看到,1秒钟中断后,就停止了。那这种场景就是前面说的"频繁地检查",导致程序效率低下;那如果不频繁地检查呢,比如在while中的else分支中加上Thread.sleep(500),表示500ms即0.5s检查一次,那这种场景就是前面说的"中断得不到及时的响应"。

  其实这个例子中,thread01线程完全可以不用去管这个中断标识位的,不去检查就好了,只管做自己的事情,这说明中断标识位设不设置是别人的事情,处不处理是我自己的事情,没有强制要求必须处理中断。按照这个例子理解就是,main线程中给thread01线程设置了中断标识位,但是thread01线程处不处理就是它自己的事情了。

  但是,那些会抛出InterruptedException的方法要除外。像sleep、wait、notify、join,这些方法遇到中断必须有对应的措施,可以直接在catch块中处理,也可以抛给上一层。这些方法之所以会抛出InterruptedException就是由于Java虚拟机在实现这些方法的时候,本身就有某种机制在判断中断标识位,如果中断了,就抛出一个InterruptedException。

  示例2:若线程被设置了中断标识位,调用sleep方法时会抛出异常

public class Thread01 extends Thread{

    @Override
public void run() { while(true){
if(Thread.currentThread().isInterrupted()){
System.out.println(Thread.currentThread().getName() + "被中断了");
break;
}else{
try {
Thread.sleep(400);
System.out.println(Thread.currentThread().getName() + "运行中");
} catch (InterruptedException e) {
e.printStackTrace();
System.out.println("sleep方法抛出了异常");
break;
}
}
} }
}

  测试:

public class Test {
public static void main(String[] args) throws InterruptedException {
Thread01 thread01 = new Thread01();
thread01.start();
Thread.sleep(1000);
thread01.interrupt();
}
}

  结果:

参考资料:

Java多线程17:中断机制

Thread中interrupted()方法和isInterrupted()方法区别总结

Java多线程9:中断机制的更多相关文章

  1. JAVA多线程之中断机制(stop()、interrupted()、isInterrupted())

    一,介绍 本文记录JAVA多线程中的中断机制的一些知识点.主要是stop方法.interrupted()与isInterrupted()方法的区别,并从源代码的实现上进行简单分析. JAVA中有3种方 ...

  2. JAVA多线程之中断机制(如何处理中断?)

    一,介绍 这篇文章主要记录使用 interrupt() 方法中断线程,以及如何对InterruptedException进行处理.感觉对InterruptedException异常进行处理是一件谨慎且 ...

  3. Java多线程——中断机制

    前言:在Java多线程中,中断一直围绕着我们,当我们阅读各种关于Java多线程的资料.书籍时,“中断”一词总是会出现,笔者对其的理解也是朦朦胧胧,因此非常有必要搞清楚Java多线程的中断机制. 1.J ...

  4. 50个Java多线程面试题

    不管你是新程序员还是老手,你一定在面试中遇到过有关线程的问题.Java 语言一个重要的特点就是内置了对并发的支持,让 Java 大受企业和程序员的欢迎.大多数待遇丰厚的 Java 开发职位都要求开发者 ...

  5. 50个Java多线程面试题(上)

    Java 语言一个重要的特点就是内置了对并发的支持,让 Java 大受企业和程序员的欢迎.大多数待遇丰厚的 Java 开发职位都要求开发者精通多线程技术并且有丰富的 Java 程序开发.调试.优化经验 ...

  6. Java多线程与并发面试题

    1,什么是线程? 线程是操作系统能够进行运算调度的最小单位,它被包含在进程之中,是进程中的实际运作单位.程序员可以通过它进行多处理器编程,你可以使用多线程对运算密集型任务提速.比如,如果一个线程完成一 ...

  7. Java多线程面试题整理

    部分一:多线程部分: 1) 什么是线程? 线程是操作系统能够进行运算调度的最小单位,它被包含在进程之中,是进程中的实际运作单位.程序员可以通过它进行多处理器编程,你可以使用多线程对运算密集型任务提速. ...

  8. JAVA多线程17个问题

    1.Thread 类中的start() 和 run() 方法有什么区别? Thread.start()方法(native)启动线程,使之进入就绪状态,当cpu分配时间该线程时,由JVM调度执行run( ...

  9. java多线程面试题整理及答案(2018年)

    1) 什么是线程? 线程是操作系统能够进行运算调度的最小单位,它被包含在进程之中,是进程中的实际运作单位.程序员可以通过它进行多处理器编程,你可以使用多线程对 运算密集型任务提速.比如,如果一个线程完 ...

  10. Java 多线程 - 转载

    下面是Java线程相关的热门面试题,你可以用它来好好准备面试. 1) 什么是线程? 线程是操作系统能够进行运算调度的最小单位,它被包含在进程之中,是进程中的实际运作单位.程序员可以通过它进行多处理器编 ...

随机推荐

  1. Java几种常见的设计模式

    --------------------- 本文来自 旭日Follow_24 的CSDN 博客 ,全文地址请点击:https://blog.csdn.net/xuri24/article/detail ...

  2. jquery中点击切换的实现

    项目中经常会遇到一种情况,就是点击切换,比如点击按钮,div样式为1,再点击一下按钮,div样式为2,再点击一下按钮,div样式为1.需要自定义jQuery方法toggle. // toggle方法 ...

  3. angular反向代理配置

    Angular-cli 是基于webpack 的一套针对提升angular开发体验的命令行工具. 开发vue的时候,基于webpack的时候当时配置一个反向代理以完全实现前后端分离的体验,既然webp ...

  4. Python第四天 流程控制 if else条件判断 for循环 while循环

    Python第四天   流程控制   if else条件判断   for循环 while循环 目录 Pycharm使用技巧(转载) Python第一天  安装  shell  文件 Python第二天 ...

  5. SQLServer之修改视图

    修改视图注意事项 修改先前创建的视图. 其中包括索引视图. ALTER VIEW不影响相关的存储过程或触发器,并且不会更改权限. 如果原来的视图定义是使用 WITH ENCRYPTION 或 CHEC ...

  6. 启动期间的内存管理之pagging_init初始化分页机制--Linux内存管理(十四)

    1 今日内容(分页机制初始化) 在初始化内存的结点和内存区域之前, 内核先通过pagging_init初始化了内核的分页机制. 在分页机制完成后, 才会开始初始化系统的内存数据结构(包括内存节点数据和 ...

  7. navicat 将自增长字段重置(重新从1开始)的方法

    先说明,此语句会将你的表中数据全部删除. 很简单,运行如下sql语句: TRUNCATE TABLE 表名;

  8. Eclipse启动报错,解决办法

    打开log日志,发现如下错误.原因是修改了计算机用户名导致 !SESSION Thu Aug 30 08:55:41 CST 2018 -------------------------------- ...

  9. Windows Server 2012 R2 配置FTP服务器

    Windows Server 2012 R2 安装IIS参考上一篇配置IIS 8.0:https://www.cnblogs.com/aq-ry/p/9329310.html 搭建完IIS 后,最近又 ...

  10. CentOS7.2重置root密码的处理方法

    第一个里程碑 --在启动GRUB菜单中选择编辑选项,按键 "e" 进入编辑; 第二个里程碑 -- 大约在第16行找到 "ro" 将 "ro" ...