zoukankan      html  css  js  c++  java
  • 线程中断方法interrupt() 与 cancel()

    (一).关于interrupt()
        interrupt()并不直接中断线程,而是设定一个中断标识,然后由程序进行中断检查,确定是否中断。
        1. sleep() & interrupt()
        线程A正在使用sleep()暂停着: Thread.sleep(100000);
        如果要取消他的等待状态,可以在正在执行的线程里(比如这里是B)调用a.interrupt();
        令线程A放弃睡眠操作,这里a是线程A对应到的Thread实例执行interrupt()时,并不需要获取Thread实例的锁定.任何线程在任何时刻,都可以调用其他线程interrupt().当sleep中的线程被调用interrupt()时,就会放弃暂停的状态.并抛出InterruptedException.丢出异常的,是A线程.
    2. wait() & interrupt()
        线程A调用了wait()进入了等待状态,也可以用interrupt()取消.
        不过这时候要小心锁定的问题.线程在进入等待区,会把锁定解除,当对等待中的线程调用interrupt()时(注意是等待的线程调用其自己的interrupt()),会先重新获取锁定,再抛出异常.在获取锁定之前,是无法抛出异常的.
    3. join() & interrupt()
        当线程以join()等待其他线程结束时,一样可以使用interrupt()取消之.因为调用join()不需要获取锁定,故与sleep()时一样,会马上跳到catch块里. 注意是随调用interrupt()方法,一定是阻塞的线程来调用其自己的interrupt方法.如在线程a中调用来线程t.join().则a会等t执行完后在执行t.join后的代码,当在线程b中调用来a.interrupt()方法,则会抛出InterruptedException
    4. interrupt()只是改变中断状态而已
        interrupt()不会中断一个正在运行的线程。这一方法实际上完成的是,在线程受到阻塞时抛出一个中断信号,这样线程就得以退出阻塞的状态。更确切的说,如果线程被Object.wait, Thread.join和Thread.sleep三种方法之一阻塞,那么,它将接收到一个中断异常(InterruptedException),从而提早地终结被阻塞状态。
        如果线程没有被阻塞,这时调用interrupt()将不起作用;否则,线程就将得到异常(该线程必须事先预备好处理此状况),接着逃离阻塞状态。
        线程A在执行sleep,wait,join时,线程B调用A的interrupt方法,的确这一个时候A会有InterruptedException异常抛出来.但这其实是在sleep,wait,join这些方法内部会不断检查中断状态的值,而自己抛出的InterruptedException。
        如果线程A正在执行一些指定的操作时如赋值,for,while,if,调用方法等,都不会去检查中断状态,所以线程A不会抛出InterruptedException,而会一直执行着自己的操作.当线程A终于执行到wait(),sleep(),join()时,才马上会抛出InterruptedException.
        若没有调用sleep(),wait(),join()这些方法,或是没有在线程里自己检查中断状态自己抛出InterruptedException的话,那InterruptedException是不会被抛出来的.   
     
    (二)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的实现和硬件的性能。   
        然而,对于某些线程阻塞操作,JVM并不会自动抛出InterruptedException异常。例如,某些I/O操作和内部锁操作。对于这类操作,可以用其他方式模拟中断:
        1)java.io中的异步socket I/O
        读写socket的时候,InputStream和OutputStream的read和write方法会阻塞等待,但不会响应java中断。不过,调用Socket的close方法后,被阻塞线程会抛出SocketException异常。
        2)利用Selector实现的异步I/O
        如果线程被阻塞于Selector.select(在java.nio.channels中),调用wakeup方法会引起ClosedSelectorException异常。
        3)锁获取
        如果线程在等待获取一个内部锁,我们将无法中断它。但是,利用Lock类的lockInterruptibly方法,我们可以在等待锁的同时,提供中断能力。
        另外,在任务与线程分离的框架中,任务通常并不知道自身会被哪个线程调用,也就不知道调用线程处理中断的策略。所以,在任务设置了线程中断标记后,并不能确保任务会被取消。因此,有以下两条编程原则:
        1)除非你知道线程的中断策略,否则不应该中断它。
            这条原则告诉我们,不应该直接调用Executer之类框架中线程的interrupt方法,应该利用诸如Future.cancel的方法来取消任务。
        2)任务代码不该猜测中断对执行线程的含义。
            这条原则告诉我们,一般代码遇在到InterruptedException异常时,不应该将其捕获后“吞掉”,而应该继续向上层代码抛出。
        总之,Java中的非抢占式中断机制,要求我们必须改变传统的抢占式中断思路,在理解其本质的基础上,采用相应的原则和模式来编程。
     
    (三) interrupt() 与 cancel()的区别
       两者实际上都是中断线程,但是后者更安全、有条理和高效,其原因跟推荐使用Executor而不直接使用Thread类是一致的。所以结合上面讲到的原则,我们应尽量采用cancel()方法,调用线程管理器ExecutorService接口的submit(Runnable task) 方法会返回一个Future<?>对象,然后调用Future.cancel()的方法来取消任务,并返回一个boolean值。

    【Future】

    image

    http://www.gznc.edu.cn/yxsz/jjglxy/book/Java_api/java/util/concurrent/Future.html

    【好奇】

    (1)future.cancel(mayInterruptIfRunning)的内部实现会是什么样子的?可以中断一个线程池里正在执行着的“那一个”任务。

    可猜想,必定记录着具体线程标识,且发了一个中断信号。

    (2)猜测,应该只是发一个中断信号,可以中断阻塞中的操作。而如果是while(true); 这样的占用CPU的非阻塞式操作,是中断不掉的,也即线程依旧在跑,占用着线程池资源。

    【注意】

    a). 线程池资源有限,有些任务会submit()不进去,抛异常:java.util.concurrent.RejectedExecutionException

    b).只要submit()成功的,无论是线程正在执行,或是在BlockingQueue中等待执行,future.cancel()操作均可中断掉线程。也即,与其真正执行并无关系,阻塞中或等待被调度执行中,都将被中断。

    【demo示例】

    future.cancel中断阻塞操作:

    public class Main {
        
        /** 信号量 */
        private Semaphore semaphore = new Semaphore(0); // 1
        
        /** 线程池 */
        private ThreadPoolExecutor pool = new ThreadPoolExecutor(3, 5, 3, TimeUnit.SECONDS, new ArrayBlockingQueue<Runnable>(3));
        
        /** Future */
        private Future<String> future ;
        
        public void test(){
            
            future = pool.submit(new Callable<String>() {
     
                @Override
                public String call() {
                    String result = null;
                    try {
                        // 同步阻塞获取信号量
                        semaphore.acquire();
                        result = "ok";
                    } catch (InterruptedException e) {
                        result = "interrupted";
                    }
                    return result;
                }
            });
            
            String result = "timeout";
            try {
                // 等待3s
                result = future.get(3, TimeUnit.SECONDS);
            }catch (Exception e) {
                System.out.println("超时异常");
            }
            
            // 删除线程池中任务
            boolean cancelResult = future.cancel(true);
            
            System.out.println("result is " + result);
            System.out.println("删除结果:"  +cancelResult);
            System.out.println("当前active线程数:" +pool.getActiveCount());
            
        }
     
        public static void main(String[] args) {
            Main o = new Main();        
            o.test();
        }
    }

     http://www.cnblogs.com/alipayhutu/archive/2012/06/20/2556091.html

    http://blog.sina.com.cn/s/blog_00ccd2400100nq7o.html

  • 相关阅读:
    JVM 综述
    看 Netty 在 Dubbo 中如何应用
    Netty 心跳服务之 IdleStateHandler 源码分析
    Netty 高性能之道
    Netty 解码器抽象父类 ByteToMessageDecoder 源码解析
    Netty 源码剖析之 unSafe.write 方法
    Netty 出站缓冲区 ChannelOutboundBuffer 源码解析(isWritable 属性的重要性)
    Netty 源码剖析之 unSafe.read 方法
    Netty 内存回收之 noCleaner 策略
    Netty 源码阅读的思考------耗时业务到底该如何处理
  • 原文地址:https://www.cnblogs.com/softidea/p/3651102.html
Copyright © 2011-2022 走看看