美文网首页
线程中断机制及响应

线程中断机制及响应

作者: 刘建会 | 来源:发表于2017-11-12 13:41 被阅读372次

    中断线程

    thread.interrupt()用来中断线程,即将线程的中断状态位设置为true,注意中断操作并不会终止线程,不像stop()会立即终止一个运行中的线程,中断仅仅是将线程中断位设置为true(默认false)。线程会不断的检查中断位,如果线程处于阻塞状态(sleep、join、wait)且中断,就会抛出InterreptException来唤醒线程,交由应用程序处理;如果线程未阻塞且中断,也要交由应用程序处理;是终止线程,还是继续执行需要根据实际情况做出合理的响应。

    如何响应线程中断

    thread.interrupt()后,线程的中断位设置为true,没有任何语言方面的需求一个被中断的线程应该终止。中断只是为了引起该线程的注意,来决定如何应对中断。有些很重要的线程,以至于他们不理会中断,而是继续执行,但更多的情况下,线程应该把中断看做是一个终止请求,在终止线程前,有时间做一些收尾清理工作,如下:

        public void run() {
            try {
                /*
                 * 不论while中是否调用过线程阻塞的方法,如sleep、join、wait,这里还是需要加上
                 * !Thread.currentThread().isInterrupted()条件来判断线程是否终止。
                 * 原因:即使调用了阻塞方法但线程可能仍没有阻塞,这样会更安全、更及时。
                 */
                while (!Thread.currentThread().isInterrupted()) {
                    //do more work
                }
            } catch (InterruptedException e) {
                //线程在阻塞期间被中断了
            } finally {
                //线程结束前做一些清理工作
            }
        }
    

    注意:这里使用Thread.currentThread().isInterrupted(),而不是Thread.isInterrupted()来判断线程是否中断,因为Thread.isInterrupted()判断线程中断位为true后,会将中断位设置为false,而Thread.currentThread().isInterrupted()不会将中端位在设置为false。线程阻塞中断是抛出异常后,同样会将中断位设置为false。

        public void run() {
            while (!Thread.currentThread().isInterrupted()) {
                try {
                    ...
                    sleep(delay);
                } catch (InterruptedException e) {
                    Thread.currentThread().interrupt();//重新设置中断位,否则线程不能退出
                }
            }
        }
    

    注意:synchornized、reentrantLock.lock()获取锁操作造成的阻塞在中断状态下不能抛出InterruptException,即获取锁操作是不能被中断的,要一直阻塞等到到它获取到锁为止。也就是说如果产生了死锁,则不能被中断。可以使用超时锁来打破死锁,reentrantLock.tryLock(timeout,unit)在timeout后会抛出InterruptException,唤醒线程做响应操作

    在捕获到InterruptException后如何处理呢,通常有如下两种方式,这两种方式本质上是一样的,都是将中断交由用户应用程序来处理。

    • 1.在catch子句中调用Thread.currentThread.interrupt()来重设中断状态位(因为抛出中断异常后,中断状态位会被清除,即false),让外界判断Thread.currentThread.isInterrupted()来决定线程的行为。
        try {
            sleep(delay);
        } catch (InterruptedException e) {
            Thread.currentThread().isInterrupted();
        }
    
      1. 更加直接,不用捕获异常,直接抛出InterruptedException,让用户程序去处理异常。
        try {
            sleep(delay);
        } catch (InterruptedException e) {
            throw e;
        }
    

    终止线程

    让一个运行中的线程终止,通常有两种方法:1.使用信号量;2.使用线程中断。

    • 1.使用信号量终止线程:设置一个变量(信号量),通过更改变量的状态,来决定线程是否要终止,该方式不能终止阻塞的线程。
    class Scheduler{
        volatile boolean stop = false;// 线程中断信号量
        private Worker worker = new Worker("my task worker");
        void start(){
            worker.start();
            stop=false;
        }
        void stop(){
            stop=true;
        }
    
        class Worker extend Thread{
            public void run() {
                while (!stop) {
                    long time = System.currentTimeMillis();
                    /*
                     * 用while循环模拟sleep(),这里不要用sleep,否则在线程阻塞时可能会抛
                     * InterruptedException而退出,这样while检测stop条件就不会执行,失去了意义。
                     *
                     * 不论是模拟sleep,还是使用sleep,都建议线程任务中做这样的操作,以控制线程执行速率
                     */
                    while ((System.currentTimeMillis() - time < 1000)) {
                        //do something
                    }
                }
            }
        }
    }
    

    注意:信号量一定要是多线程可见的,这里使用volatile关键字,也可以使用Automicxxx

    • 2.使用线程中断来终止线程:用thread.interrupt()设置线程中断位为true,让应用来决定线程是否要终止,该方式能让阻塞的线程抛出InterruptException来唤醒线程,从而决定线程的行为。
    class Scheduler{
        private Worker worker = new Worker("my task worker");
        void start(){
            worker.start();
        }
        void stop(){
            worker.interrupt();//设置线程中断位为true
        }
    
        class Worker extend Thread{
            public void run() {
                while (!Thread.currentThread.isInterrupted()) {
                    try{
                        // do something
                        Thread.sleep(10);
                    }catch(InterruptException e){
                        // 重新设置线程状态位
                        Thread.currentThread.interrupted();
                    }
                }
            }
        }
    }
    

    注意:这里也可以使用信号量代替Thread.currentThread.isInterrupted(),在捕获到异常后,设置信号量即可,但不太简便。

    I/O阻塞中断

    i/o操作可以阻塞线程很长时间,特别是牵扯到网络socket时,那么在i/o阻塞时,线程中断会有什么现象呢?有两种情况:

    • 1.线程在可中断通道(实现InterruptibleChannel接口)上,调用io阻塞操作(serverSocketChannel.accept(),socketChannel.connect(),socketChannel.open(),socketChannel.read(),socketChannel.write()等)而阻塞线程后,又被设置为中断时(调用thread.interrupt()),通道将被关闭,并在io阻塞处会抛出ClosedByInterruptException,这样应用程序就能响应改中断异常,做出合适的处理。
    • 2.线程在不可中断通道(java1.0前的传统io模型)上,调用io阻塞操作,即使线程被设置为中断,也不会唤醒阻塞线程,因为这样的io阻塞在线程中断后不会抛出任何异常。幸运的是java平台提供了一种解决方案:调用阻塞改线程的socket的close方法,此时在socket.accept()处就抛出SocketException,这与thread.interrupt()
      引起InterruptException抛出很相似,可以响应SocketException来终止并退出线程。

    总结

    • 1.中断一个线程,只是为了引起该线程的注意(唤醒阻塞),让其自己决定如何应对,而不是像stop一样直接终止掉线程。
    • 2.阻塞的线程如果被中断,会在阻塞处抛出InterruotException来唤醒线程,并将线程中断标志位设置为false,因为线程已经处理了异常(抛出),就要回到就绪状态。
    • 3.synchronized,Lock.lock获取锁操作不能被中断,如果拿不到资源,会一直阻塞下去,可以使用可中断的加锁操作Lock.lockInterruptibly(),也可以使用超时锁Lock.tryLock(timeout,unit),在超时后会抛出异常。
    • 4.中断其实是线程的一种协作机制,比直接结束线程操作弱一点,给程序一些时间来决定如何处理,如sleep方法对中断的处理是抛出中断异常,并重置中断位置。

    相关文章

      网友评论

          本文标题:线程中断机制及响应

          本文链接:https://www.haomeiwen.com/subject/siwnmxtx.html