Java中断机制

1. 引言

  当咱们点击某个杀毒软件的取消按钮来中止查杀病毒时,当咱们在控制台敲入quit命令以结束某个后台服务时……都须要经过一个线程去取消另外一个线程正在执行的任务。Java没有提供一种安全直接的方法来中止某个线程,可是Java提供了中断机制。java

  若是对Java中断没有一个全面的了解,可能会误觉得被中断的线程将立马退出运行,但事实并不是如此。中断机制是如何工做的?捕获或检测到中断后,是抛出InterruptedException仍是重设中断状态以及在方法中吞掉中断状态会有什么后果?Thread.stop与中断相比又有哪些异同?什么状况下须要使用中断?本文将从以上几个方面进行描述。编程

2. 中断的原理

  Java中断机制是一种协做机制,也就是说经过中断并不能直接终止另外一个线程,而须要被中断的线程本身处理中断。这比如是家里的父母叮嘱在外的子女要注意身体,但子女是否注意身体,怎么注意身体则彻底取决于本身。安全

  Java中断模型也是这么简单,每一个线程对象里都有一个boolean类型的标识(不必定就要是Thread类的字段,实际上也的确不是,这几个方法最终都是经过native方法来完成的),表明着是否有中断请求(该请求能够来自全部线程,包括被中断的线程自己)。例如,当线程t1想中断线程t2,只须要在线程t1中将线程t2对象的中断标识置为true,而后线程2能够选择在合适的时候处理该中断请求,甚至能够不理会该请求,就像这个线程没有被中断同样。bash

java.lang.Thread类提供了几个方法来操做这个中断状态,这些方法包括:并发

  • public static boolean interrupted
    测试当前线程是否已经中断。线程的dom

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

  • public boolean isInterrupted()
    测试线程是否已经中断。线程的异步

    中断状态
    不受该方法的影响。

  • public void interrupt()
    中断线程。性能

其中,interrupt方法是惟一能将中断状态设置为true的方法。静态方法interrupted会将当前线程的中断状态清除,但这个方法的命名极不直观,很容易形成误解,须要特别注意。学习

上面的例子中,线程t1经过调用interrupt方法将线程t2的中断状态置为true,t2能够在合适的时候调用interrupted或isInterrupted来检测状态并作相应的处理。测试

  此外,类库中的有些类的方法也可能会调用中断,如FutureTask中的cancel方法,若是传入的参数为true,它将会在正在运行异步任务的线程上调用interrupt方法,若是正在执行的异步任务中的代码没有对中断作出响应,那么cancel方法中的参数将不会起到什么效果;又如ThreadPoolExecutor中的shutdownNow方法会遍历线程池中的工做线程并调用线程的interrupt方法来中断线程,因此若是工做线程中正在执行的任务没有对中断作出响应,任务将一直执行直到正常结束。

3. 中断的处理

既然Java中断机制只是设置被中断线程的中断状态,那么被中断线程该作些什么?

处理时机

  显然,做为一种协做机制,不会强求被中断线程必定要在某个点进行处理。实际上,被中断线程只需在合适的时候处理便可,若是没有合适的时间点,甚至能够不处理,这时候在任务处理层面,就跟没有调用中断方法同样。“合适的时候”与线程正在处理的业务逻辑紧密相关,例如,每次迭代的时候,进入一个可能阻塞且没法中断的方法以前等,但多半不会出如今某个临界区更新另外一个对象状态的时候,由于这可能会致使对象处于不一致状态。

  处理时机决定着程序的效率与中断响应的灵敏性。频繁的检查中断状态可能会使程序执行效率降低,相反,检查的较少可能使中断请求得不到及时响应。若是发出中断请求以后,被中断的线程继续执行一段时间不会给系统带来灾难,那么就能够将中断处理放到方便检查中断,同时又能从必定程度上保证响应灵敏度的地方。当程序的性能指标比较关键时,可能须要创建一个测试模型来分析最佳的中断检测点,以平衡性能和响应灵敏性。

处理方式

一、 中断状态的管理

通常说来,当可能阻塞的方法声明中有抛出InterruptedException则暗示该方法是可中断的,如BlockingQueue#put、BlockingQueue#take、Object#wait、Thread#sleep等,若是程序捕获到这些可中断的阻塞方法抛出的InterruptedException或检测到中断后,这些中断信息该如何处理?通常有如下两个通用原则:

  • 若是遇到的是可中断的阻塞方法抛出InterruptedException,能够继续向方法调用栈的上层抛出该异常,若是是检测到中断,则可清除中断状态并抛出InterruptedException,使当前方法也成为一个可中断的方法。
  • 如有时候不太方便在方法上抛出InterruptedException,好比要实现的某个接口中的方法签名上没有throws InterruptedException,这时就能够捕获可中断方法的InterruptedException并经过Thread.currentThread.interrupt()来从新设置中断状态。若是是检测并清除了中断状态,亦是如此。

  通常的代码中,尤为是做为一个基础类库时,毫不应当吞掉中断,即捕获到InterruptedException后在catch里什么也不作,清除中断状态后又不重设中断状态也不抛出InterruptedException等。由于吞掉中断状态会致使方法调用栈的上层得不到这些信息。

固然,凡事总有例外的时候,当你彻底清楚本身的方法会被谁调用,而调用者也不会由于中断被吞掉了而遇到麻烦,就能够这么作。

  总得来讲,就是要让方法调用栈的上层获知中断的发生。假设你写了一个类库,类库里有个方法amethod,在amethod中检测并清除了中断状态,而没有抛出InterruptedException,做为amethod的用户来讲,他并不知道里面的细节,若是用户在调用amethod后也要使用中断来作些事情,那么在调用amethod以后他将永远也检测不到中断了,由于中断信息已经被amethod清除掉了。若是做为用户,遇到这样有问题的类库,又不能修改代码,那该怎么处理?只好在本身的类里设置一个本身的中断状态,在调用interrupt方法的时候,同时设置该状态,这实在是无路可走时才使用的方法。

二、 中断的响应

  程序里发现中断后该怎么响应?这就得视实际状况而定了。有些程序可能一检测到中断就立马将线程终止,有些多是退出当前执行的任务,继续执行下一个任务……做为一种协做机制,这要与中断方协商好,当调用interrupt会发生些什么都是事先知道的,如作一些事务回滚操做,一些清理工做,一些补偿操做等。若不肯定调用某个线程的interrupt后该线程会作出什么样的响应,那就不该当中断该线程。

4. Thread.interrupt VS Thread.stop

  Thread.stop方法已经不推荐使用了。而在某些方面Thread.stop与中断机制有着类似之处。如当线程在等待内置锁或IO时,stop跟interrupt同样,不会停止这些操做;当catch住stop致使的异常时,程序也能够继续执行,虽然stop本意是要中止线程,这么作会让程序行为变得更加混乱。

那么它们的区别在哪里?最重要的就是中断须要程序本身去检测而后作相应的处理,而Thread.stop会直接在代码执行过程当中抛出ThreadDeath错误,这是一个java.lang.Error的子类。

在继续以前,先来看个小例子:

复制代码
package com.ticmy.interrupt;
import java.util.Arrays;
import java.util.Random;
import java.util.concurrent.TimeUnit;
public class TestStop {
    private static final int[] array = new int[80000];
    private static final Thread t = new Thread() {
        public void run() {
            try {
                System.out.println(sort(array));
            } catch (Error err) {
                err.printStackTrace();
            }
            System.out.println("in thread t");
        }
    };

    static {
        Random random = new Random();
        for(int i = 0; i < array.length; i++) {
            array[i] = random.nextInt(i + 1);
        }
    }

    private static int sort(int[] array) {
        for (int i = 0; i < array.length-1; i++){
            for(int j = 0 ;j < array.length - i - 1; j++){
                if(array[j] < array[j + 1]){
                    int temp = array[j];
                    array[j] = array[j + 1];
                    array[j + 1] = temp;
                }
            }
        }
        return array[0];
    }

    public static void main(String[] args) throws Exception {
        t.start();
        TimeUnit.SECONDS.sleep(1);
        System.out.println("go to stop thread t");
        t.stop();
        System.out.println("finish main");
    }
}复制代码

这个例子很简单,线程t里面作了一个很是耗时的排序操做,排序方法中,只有简单的加、减、赋值、比较等操做,一个可能的执行结果以下:

go to stop thread t
java.lang.ThreadDeath
    at java.lang.Thread.stop(Thread.java:758)
    at com.ticmy.interrupt.TestStop.main(TestStop.java:44)
finish main
in thread t复制代码

这里sort方法是个很是耗时的操做,也就是说主线程休眠一秒钟后调用stop的时候,线程t还在执行sort方法。就是这样一个简单的方法,也会抛出错误!换一句话说,调用stop后,大部分Java字节码都有可能抛出错误,哪怕是简单的加法!

若是线程当前正持有锁,stop以后则会释放该锁。因为此错误可能出如今不少地方,那么这就让编程人员防不胜防,极易形成对象状态的不一致。例如,对象obj中存放着一个范围值:最小值low,最大值high,且low不得大于high,这种关系由锁lock保护,以免并发时产生竞态条件而致使该关系失效。假设当前low值是5,high值是10,当线程t获取lock后,将low值更新为了15,此时被stop了,真是糟糕,若是没有捕获住stop致使的Error,low的值就为15,high仍是10,这致使它们之间的小于关系得不到保证,也就是对象状态被破坏了!若是在给low赋值的时候catch住stop致使的Error则可能使后面high变量的赋值继续,可是谁也不知道Error会在哪条语句抛出,若是对象状态之间的关系更复杂呢?这种方式几乎是没法维护的,太复杂了!若是是中断操做,它决计不会在执行low赋值的时候抛出错误,这样程序对于对象状态一致性就是可控的。

正是由于可能致使对象状态不一致,stop才被禁用。

5. 中断的使用

一般,中断的使用场景有如下几个:

  • 点击某个桌面应用中的取消按钮时;
  • 某个操做超过了必定的执行时间限制须要停止时;
  • 多个线程作相同的事情,只要一个线程成功其它线程均可以取消时;
  • 一组线程中的一个或多个出现错误致使整组都没法继续时;
  • 当一个应用或服务须要中止时。

下面来看一个具体的例子。这个例子里,本打算采用GUI形式,但考虑到GUI代码会使程序复杂化,就使用控制台来模拟下核心的逻辑。这里新建了一个磁盘文件扫描的任务,扫描某个目录下的全部文件并将文件路径打印到控制台,扫描的过程可能会很长。若须要停止该任务,只需在控制台键入quit并回车便可。


package com.ticmy.interrupt;
import java.io.BufferedReader;
import java.io.File;
import java.io.InputStreamReader;

public class FileScanner {
    private static void listFile(File f) throws InterruptedException {
        if(f == null) {
            throw new IllegalArgumentException();
        }
        if(f.isFile()) {
            System.out.println(f);
            return;
        }
        File[] allFiles = f.listFiles();
        if(Thread.interrupted()) {
            throw new InterruptedException("文件扫描任务被中断");
        }
        for(File file : allFiles) {
            //还能够将中断检测放到这里
            listFile(file);
        }
    }

    public static String readFromConsole() {
        BufferedReader reader = new BufferedReader(new InputStreamReader(System.in));
        try {
            return reader.readLine();
        } catch (Exception e) {
            e.printStackTrace();
            return "";
        }
    }

    public static void main(String[] args) throws Exception {
        final Thread fileIteratorThread = new Thread() {
            public void run() {
                try {
                    listFile(new File("c:\\"));
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        };
        new Thread() {
            public void run() {
                while(true) {
                    if("quit".equalsIgnoreCase(readFromConsole())) {
                        if(fileIteratorThread.isAlive()) {
                            fileIteratorThread.interrupt();
                            return;
                        }
                    } else {
                        System.out.println("输入quit退出文件扫描");
                    }
                }
            }
        }.start();
        fileIteratorThread.start();
    }
}复制代码

今天的技术分享就到这里,欢迎你们扫下方二维码加学习技术交流群,一块儿夯实基础,提高自我价值。

相关文章
相关标签/搜索