Java提供了中断机制,可以使用它来结束一个线程。这种机制要求线程检查它是否被中断了,然后决定是不是响应这个中断请求。线程允许忽略中断请求并继续执行。Java的中断是一种协作机制。也就是说调用线程对象的interrupt方法并不一定就中断了正在运行的线程,它只是要求线程自己在合适的时机中断自己。Thread类有一个表明线程被中断的属性,它存放boolean值。线程的interrupted()方法被调用时,该值设为true。isInterrupted()方法返回这个属性的值。

  一、Java中断的现象
  首先,看看Thread类里的几个方法:

public static boolean

interrupted()

测试当前线程是否已经中断。线程的中断状态由该方法清除。换句话说,如果连续两次调用该方法,则第二次调用将返回 false(在第一次调用已清除了其中断状态之后,且第二次调用检验完中断状态前,当前线程再次中断的情况除外)。

public boolean

isInterrupted()

测试线程是否已经中断。线程的中断状态不受该方法的影响。
public void interrupt() 中断线程。

  上面列出了与中断有关的几个方法及其行为,可以看到interrupt是中断线程。如果不了解Java的中断机制,这样的一种解释极容易造成误解,认为调用了线程的interrupt方法就一定会中断线程。 Java的中断是一种协作机制。也就是说调用线程对象的interrupt方法并不一定就中断了正在运行的线程,它只是要求线程自己在合适的时机中断自己。每个线程都有一个boolean的中断状态(不一定就是对象的属性,事实上,该状态也确实不是Thread的字段),interrupt方法仅仅只是将该状态置为true 。代码如下: 

public class Test {
public static void main(String[] args) {
Thread t = new MyThread();
t.start();
t.interrupt();
System.out.println("已调用线程的interrupt方法");
} static class MyThread extends Thread {
public void run() {
int num = longTimeRunningNonInterruptMethod(2, 0);
System.out.println("长时间任务运行结束,num=" + num);
System.out.println("线程的中断状态:" + Thread.interrupted());
} private static int longTimeRunningNonInterruptMethod(int count,int initNum) {
for (int i = 0; i < count; i++) {
for (int j = 0; j < Integer.MAX_VALUE; j++) {
initNum++;
}
}
return initNum;
}
}
}

  一般情况下,会打印如下内容:

已调用线程的interrupt方法
长时间任务运行结束,num=-2
线程的中断状态:true

  可见,interrupt方法并不一定能中断线程。但是,如果改成下面的程序,情况会怎样呢? 代码如下:

public class Test {
public static void main(String[] args) {
Thread t = new MyThread();
t.start();
t.interrupt();
System.out.println("已调用线程的interrupt方法");
}
static class MyThread extends Thread {
public void run() {
int num = -1;
try {
num = longTimeRunningInterruptMethod(2, 0);
} catch (InterruptedException e) {
System.out.println("线程被中断");
throw new RuntimeException(e);
}
System.out.println("长时间任务运行结束,num=" + num);
System.out.println("线程的中断状态:" + Thread.interrupted());
}
private static int longTimeRunningInterruptMethod(int count, int initNum)
throws InterruptedException {
for (int i = 0; i < count; i++) {
TimeUnit.SECONDS.sleep(5);
}
return initNum;
}
}
}
  运行结果如下:

已调用线程的interrupt方法
线程被中断
Exception in thread "Thread-0" java.lang.RuntimeException: java.lang.InterruptedException: sleep interrupted at Chapter1.Test$MyThread.run(Test.java:20)
  Caused by: java.lang.InterruptedException: sleep interrupted
   at java.lang.Thread.sleep(Native Method)
  at java.lang.Thread.sleep(Thread.java:340)
  at java.util.concurrent.TimeUnit.sleep(TimeUnit.java:360)
  at Chapter1.Test$MyThread.longTimeRunningInterruptMethod(Test.java:29)
  at Chapter1.Test$MyThread.run(Test.java:17)

  经运行可以发现,程序抛出异常停止了,run方法里的后两条打印语句没有执行。那么,区别在哪里?
  一
般说来,如果一个方法声明抛出InterruptedException,表示该方法是可中断的(没有在方法中处理中断却也声明抛出
InterruptedException的除外),也就是说可中断方法会对interrupt调用做出响应(例如sleep响应interrupt的操作包括清除中断状态,抛出InterruptedException),如果interrupt调用是在可中断方法之前调用,可中断方法一定会处理中断,像上面的例子,interrupt方法极可能在run未进入sleep的时候就调用了,但sleep检测到中断,就会处理该中断。如果在可中断方法正在执行中的时候调用interrupt,会怎么样呢?这就要看可中断方法处理中断的时机了,只要可中断方法能检测到中断状态为true,就应该处理中断。让我们为开头的那段代码加上中断处理。 那么自定义的可中断方法该如何处理中断呢?那就是在适合处理中断的地方检测线程中断状态并处理。代码如下:

public class Test {
public static void main(String[] args) throws Exception {
Thread t = new MyThread();
t.start();
TimeUnit.MILLISECONDS.sleep(1);//如果不能看到处理过程中被中断的情形,可以启用这句再看看效果
t.interrupt();
System.out.println("已调用线程的interrupt方法");
}
static class MyThread extends Thread {
public void run() {
int num;
try {
num = longTimeRunningNonInterruptMethod(2, 0);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
System.out.println("长时间任务运行结束,num=" + num);
System.out.println("线程的中断状态:" + Thread.interrupted());
}
private static int longTimeRunningNonInterruptMethod(int count,int initNum) throws InterruptedException {
if (interrupted()) {
throw new InterruptedException("正式处理前线程已经被请求中断");
}
for (int i = 0; i < count; i++) {
for (int j = 0; j < Integer.MAX_VALUE; j++) {
initNum++;
}
// 假如这就是一个合适的地方
if (interrupted()) {
// 回滚数据,清理操作等
throw new InterruptedException("线程正在处理过程中被中断");
}
}
return initNum;
}
}
}

  如上面的代码,方法longTimeRunningMethod此时已是一个可中断的方法了。在进入方法的时候判断是否被请求中断,如果是,就不进行相应的处理了;处理过程中,可能也有合适的地方处理中断,例如上面最内层循环结束后。这段代码中检测中断用了Thread的静态方法interrupted,它将中断状态置为false,并将之前的状态返回,而isInterrupted只是检测中断,并不改变中断状态。一般来说,处理过了中断请求,应该将其状态置为false。但具体还要看实际情形。
  

二、Java中断的本质
  在历史上,Java试图提供过抢占式限制中断,但问题多多,例如已被废弃的Thread.stop、Thread.suspend和
Thread.resume等。另一方面,出于Java应用代码的健壮性的考虑,降低了编程门槛,减少不清楚底层机制的程序员无意破坏系统的概率。 如今,Java的线程调度不提供抢占式中断,而采用协作式的中断。其实,协作式的中断,原理很简单,就是轮询某个表示中断的标记,我们在任何普通代码的中都可以实现。 例如下面的代码: 

volatile bool isInterrupted;
//…
while(!isInterrupted) {
compute();
}

  但是,上述的代码问题也很明显。当compute执行时间比较长时,中断无法及时被响应。另一方面,利用轮询检查标志变量的方式,想要中断wait和sleep等线程阻塞操作也束手无策。 如果仍然利用上面的思路,要想让中断及时被响应,必须在虚拟机底层进行线程调度的对标记变量进行检查。是的,JVM中确实是这样做的。下面摘自java.lang.Thread的源代码:

public static boolean interrupted() {
    return currentThread().isInterrupted(true);
  }
  //…
private native boolean isInterrupted(boolean ClearInterrupted);

  可以发现,isInterrupted被声明为native方法,取决于JVM底层的实现。 实际上,JVM内部确实为每个线程维护了一个中断标记。但应用程序不能直接访问这个中断变量,必须通过下面几个方法进行操作: 

public class Thread {
    //设置中断标记
    public void interrupt() { ... }
    //获取中断标记的值
    public boolean isInterrupted() { ... }
    //清除中断标记,并返回上一次中断标记的值
    public static boolean interrupted() { ... }
    ...
}

  通常情况下,调用线程的interrupt方法,并不能立即引发中断,只是设置了JVM内部的中断标记。因此,通过检查中断标记,应用程序可以做一些特殊操作,也可以完全忽略中断。你可能想,如果JVM只提供了这种简陋的中断机制,那和应用程序自己定义中断变量并轮询的方法相比,基本也没有什么优势。JVM内部中断变量的主要优势,就是对于某些情况,提供了模拟自动“中断陷入”的机制。
  在执行涉及线程调度的阻塞调用时(例如wait、sleep和join),如果发生中断,被阻塞线程会“尽可能快的”抛出InterruptedException。因此,我们就可以用下面的代码框架来处理线程阻塞中断.代码如下:

 try {
    //wait、sleep或join
  } catch(InterruptedException e) {
    //某些中断处理工作
  }

  所谓“尽可能快”,我猜测JVM就是在线程调度调度的间隙检查中断变量,速度取决于JVM的实现和硬件的性能。
三、一些不会抛出 InterruptedException 的线程阻塞操作
  然而,对于某些线程阻塞操作,JVM并不会自动抛出InterruptedException异常。例如,某些I/O操作和内部锁操作。对于这类操作,可以用其他方式模拟中断:

  • java.io中的异步socket I/O。读写socket的时候,InputStream和OutputStream的read和write方法会阻塞等待,但不会响应java中断。不过,调用Socket的close方法后,被阻塞线程会抛出SocketException异常。
  • 利用Selector实现的异步I/O 。如果线程被阻塞于Selector.select(在java.nio.channels中),调用wakeup方法会引起ClosedSelectorException异常。
  • 锁获取 。如果线程在等待获取一个内部锁,我们将无法中断它。但是,利用Lock类的lockInterruptibly方法,我们可以在等待锁的同时,提供中断能力。

四、两条编程原则
  另外,在任务与线程分离的框架中,任务通常并不知道自身会被哪个线程调用,也就不知道调用线程处理中断的策略。所以,在任务设置了线程中断标记后,并不能确保任务会被取消。因此,有以下两条编程原则:

  • 除非你知道线程的中断策略,否则不应该中断它。这条原则告诉我们,不应该直接调用Executer之类框架中线程的interrupt方法,应该利用诸如Future.cancel的方法来取消任务。
  • 任务代码不该猜测中断对执行线程的含义。这条原则告诉我们,一般代码遇在到InterruptedException异常时,不应该将其捕获后“吞掉”,而应该继续向上层代码抛出。

  总之,Java中的非抢占式中断机制,要求我们必须改变传统的抢占式中断思路,在理解其本质的基础上,采用相应的原则和模式来编程

JAVA中断机制详解的更多相关文章

  1. Java内部类详解

    Java内部类详解 说起内部类这个词,想必很多人都不陌生,但是又会觉得不熟悉.原因是平时编写代码时可能用到的场景不多,用得最多的是在有事件监听的情况下,并且即使用到也很少去总结内部类的用法.今天我们就 ...

  2. 黑马----JAVA迭代器详解

    JAVA迭代器详解 1.Interable.Iterator和ListIterator 1)迭代器生成接口Interable,用于生成一个具体迭代器 public interface Iterable ...

  3. C++调用JAVA方法详解

    C++调用JAVA方法详解          博客分类: 本文主要参考http://tech.ccidnet.com/art/1081/20050413/237901_1.html 上的文章. C++ ...

  4. Java虚拟机详解----JVM常见问题总结

    [声明] 欢迎转载,但请保留文章原始出处→_→ 生命壹号:http://www.cnblogs.com/smyhvae/ 文章来源:http://www.cnblogs.com/smyhvae/p/4 ...

  5. [转] Java内部类详解

    作者:海子 出处:http://www.cnblogs.com/dolphin0520/ 本博客中未标明转载的文章归作者海子和博客园共有,欢迎转载,但未经作者同意必须保留此段声明,且在文章页面明显位置 ...

  6. Java面向对象详解

    Java面向对象详解 前言:接触项目开发也有很长一段时间了,最近开始萌发出想回过头来写写以前学 过的基础知识的想法.一是原来刚开始学习接触编程,一个人跌跌撞撞摸索着往前走,初学的时候很多东西理解的也懵 ...

  7. java 乱码详解_jsp中pageEncoding、charset=UTF -8"、request.setCharacterEncoding("UTF-8")

    http://blog.csdn.net/qinysong/article/details/1179480 java 乱码详解__jsp中pageEncoding.charset=UTF -8&quo ...

  8. java 泛型详解(普通泛型、 通配符、 泛型接口)

    java 泛型详解(普通泛型. 通配符. 泛型接口) JDK1.5 令我们期待很久,可是当他发布的时候却更换版本号为5.0.这说明Java已经有大幅度的变化.本文将讲解JDK5.0支持的新功能---- ...

  9. Java synchronized 详解

    Java synchronized 详解 Java语言的关键字,当它用来修饰一个方法或者一个代码块的时候,能够保证在同一时刻最多只有一个线程执行该段代码. 1.当两个并发线程访问同一个对象object ...

随机推荐

  1. java实现八种排序算法并测试速度(详细)

    算法代码: /** * Created by CLY on 2017/3/17. */ package pers.cly.sorting; /** * 排序工具类,里面包含各种排序方法 */ publ ...

  2. vuejs子组件向父组件传递数据

    子组件通过$emit方法向父组件发送数据,子组件在父组件的模板中,通过自定义事件接收到数据,并通过自定义函数操作数据 <!DOCTYPE html> <html lang=" ...

  3. 老李分享:Mac快捷键

    poptest是国内唯一一家培养测试开发工程师的培训机构,以学员能胜任自动化测试,性能测试,测试工具开发等工作为目标.如果对课程感兴趣,请大家咨询qq:908821478,咨询电话010-845052 ...

  4. Linux简介与厂商版本上

    Linux简介与厂商版本   作者:Vamei 出处:http://www.cnblogs.com/vamei 欢迎转载,也请保留这段声明.谢谢! 1. Linux简介 Linux可以有狭义和广义两种 ...

  5. DC平衡双向控制解串器 转接IC GM8914:FPD-LINK III转LVTTL芯片

    1 概述 GM8914型DC平衡双向控制解串器,其主要功能是实现将2.8Gbps高速串行数据转换成10或12位并行控制信号,并同步输出一路时钟信号:同时低速通道将芯片控制信息调制到高速差分信号上传输给 ...

  6. RabbitMQ-从基础到实战(6)— 与Spring集成

    0.目录 RabbitMQ-从基础到实战(1)- Hello RabbitMQ RabbitMQ-从基础到实战(2)- 防止消息丢失 RabbitMQ-从基础到实战(3)- 消息的交换(上) Rabb ...

  7. Bug 笔记

    1.页面返回 400 Bag request: 原因:使用Spring  MVC  controller的时候,查询数据库:当数据库的数据类型是int型时,Spring MVC在查询的数据匹配给实体类 ...

  8. Java 静态代理与动态代理

    代理模式 设想你的项目依赖第三方,但是你需要对其接口做一些数据检验.性能数据记录.异常处理等,合适的方法就是使用设计模式里的代理模式. 代理模式是常用的java设计模式,代理类与委托类有同样的接口,代 ...

  9. 【模板】Tarjan求强连通分量

    有人说这篇博客不是很友好,所以我加了点解释,感觉是不是友好多了? dfn[u]表示节点u在dfs时被访问的次序. low[u]表示节点u能够追溯到的最远的祖先的dfn. ins[u]表示节点u是否在栈 ...

  10. 日历上添加活动通知(Asp.net)

    <div id="calendar_contain"> </div> <script language="javascript" ...