并发基础(八) java线程的中断机制

一、线程中断机制的原理

  java中断机制是一种协作机制,也就是说通过中断并不能直接终止另一个线程,而需要被中断的线程自己处理中断。 中断 可以认为是线程之间的一种通信的消息类型,当线程接到了一个中断消息时,怎么处理是线程的控制的,甚至可以不处理,其他线程无法干涉。
在Core Java中有这样一句话:

没有任何语言方面的需求要求一个被中断的程序应该终止。中断一个线程只是为了引起该线程的注意,被中断线程可以决定如何应对中断 “。

下面的例子中,线程便是没有对中断进行任何处理,直接忽略掉中断

Thread myThread = new Thread("myThread"){
        @Override
        public void run() {
            while(true){
                    sleep(500);
                    System.out.println("线程"+getName()+"在运行中");
                }
};
    //线程myThread启动  
    myThread.start();
    //中断线程myThread
    myThread.interrupt();

二、与中断相关的Thread类的方法

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

2、public boolean isInterrupted() :测试线程是否已经中断。线程的中断状态不受该方法的影响。
3、public static boolean interrupted() :测试当前线程是否已经中断。调用这个方法的同时,会去掉线程的中断状态。换句话说,如果连续两次调用该方法,则第二次调用将返回 false;

三、中断的处理

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

1、处理时机

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

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

2、处理方式

2.1、 中断状态的管理
  • 抛出InterruptedException的可中断的阻塞

  一般说来,当可能阻塞的方法声明中有抛出InterruptedException则暗示该方法是可中断的,如BlockingQueue的put( )、BlockingQueue的take( )、Object的wait( )、Thread的sleep()、join( )方法等,如果程序捕获到这些可中断的阻塞方法抛出的InterruptedException或检测到中断后,这些中断信息该如何处理?一般有以下两个通用原则:
  如果遇到的是可中断的阻塞方法抛出InterruptedException,可以继续向方法调用栈的上层抛出该异常,如果是检测到中断,则可清除中断状态并抛出InterruptedException,使当前方法也成为一个可中断的方法。
若有时候不太方便在方法上抛出InterruptedException,比如要实现的某个接口中的方法签名上没有throws InterruptedException,这时就可以捕获可中断方法的InterruptedException并通过Thread.currentThread.interrupt()来重新设置中断状态。如果是检测并清除了中断状态,亦是如此。
  一般的代码中,尤其是作为一个基础类库时,绝不应当吞掉中断,即捕获到InterruptedException后在catch里什么也不做,清除中断状态后又不重设中断状态也不抛出InterruptedException等。因为吞掉中断状态会导致方法调用栈的上层得不到这些信息。当然,凡事总有例外的时候,当你完全清楚自己的方法会被谁调用,而调用者也不会因为中断被吞掉了而遇到麻烦,就可以这么做。

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

  • 能响应中断,但不抛出异常的阻塞
    新Lock锁提供了等待锁资源时可立即响应中断的lockInterruptibly()方法和tryLock(long time, TimeUnit unit)方法及其实现,当使用这两个方法去请求锁时,如果主通过Thread.interrupt()对它们发起中断,那么它们会立即响应中断,不再继续等待获取锁,这让主线程(管理调度线程)在特殊情况下可以使用生杀大权,以避免系统陷于死锁状态或者避免资源严重浪费。

  • 不可中断的阻塞
    synchronized获取对象锁、I/O操作(读写)都是不可中断的,也就是说,不用捕获中断异常,执行的过程中,是不会被中断给打断的;

2.2、 中断的响应

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

注意:interrupted()方法、InterruptedException 都会吞掉中断,这时候要重新设置线程的中断状态;

下面是InterruptedException 把中断吞掉的例子 :

    Thread myThread = new Thread("myThread"){
        @Override
        public void run() {
            //判断中断有没有到来
            while(!isInterrupted()){
                try {
                    sleep(500);
                    System.out.println("线程"+getName()+"在运行中");
                } catch (InterruptedException e) {//抛出异常的同时,清除中断位
                    //重新设置线程的中断状态,这里注释掉来测试一下
                    //interrupt();
                    e.printStackTrace();
                }}}};
    //线程myThread启动  
    myThread.start();
    try {
        //main线程休眠1秒,1秒后才中断线程myThread
        Thread.sleep(1000);
    } catch (InterruptedException e1) {
        e1.printStackTrace();
    }
    //中断线程myThread
    myThread.interrupt();

运行结果:
并发基础(八) java线程的中断机制_第1张图片

InterruptedException 吞掉了中断状态的结果:run方法没有跳出循环,线程也没有结束;
只要把 interrupt( ) 方法的注释去掉,线程便可以确保在中断来时结束。警惕一下,上面的错误是很多新手都会犯。

四、中断的使用场景

通常,中断的使用场景有以下几个:
● 点击某个桌面应用中的取消按钮时;
● 某个操作超过了一定的执行时间限制需要中止时;
● 多个线程做相同的事情,只要一个线程成功其它线程都可以取消时;
● 一组线程中的一个或多个出现错误导致整组都无法继续时;
● 当一个应用或服务需要停止时。(就是上面的例子)


参考文献:

  • 详细分析Java中断机制
  • http://blog.csdn.net/jingsuwen1/article/details/51839252

你可能感兴趣的:(一,一,一☛,java线程安全✯,一,一,一☛,并发基础知识✯)