美文网首页
【Java进阶营】Java技术专题-线程Interrupt实现机

【Java进阶营】Java技术专题-线程Interrupt实现机

作者: 梦幻小孩斋 | 来源:发表于2022-05-31 15:22 被阅读0次

    内容介绍

    interrupt()方法和InterruptException异常,是java专门用来处理线程阻塞的。

    • 线程阻塞,就表示要等待一段时间。如果需要等待的时间比较长,正常还没结束之前想中断某个线程的阻塞状态怎么办?

      • 这就是靠interrupt()方法来解决了。如果因为一些特殊的原因,想提前中断一些阻塞的线程,以让他们提前解除阻塞状态,然后继续执行下去。

      • 只需要在其他线程调用指定线程的interrupt()方法即可(interrupt()方法是线程实例方法),这时候原来阻塞的对应的线程就会抛出InterruptException异常,通过catch捕获异常就可以继续往下面执行了。

    比如线程方法sleep()和Object的实例方法wait(),都会导致当前线程阻塞,这时候就可以通过interrupt()方法来提前退出阻塞状态。

    中断解释

    为什么Interrupt()方法可以提前中断阻塞呢?

    • 因为每个线程都会有一个中断状态位,暂且叫做interruptStatus吧。当前执行sleep()和wait()这些方法的时候,当前线程会把该interrruptStatus状态位设置为true,以标识当前线程为阻塞状态。

    • 当调用该线程的Interrupt()方法的话,就会重置interruptStatus状态为为false。而sleep()和wait()方法内部会不断地轮询检查InterruptStatus状态值,如果某一时刻变为false的时候,当前线程就会中断阻塞状态,通过抛出InterrupException的方式来中断阻塞状态,然后继续执行下去。

    /**
    * interrupt()方法、isInterrupted()方法、interrupted()方法
    */
    public class Interrupt {
        public static void main(String[] args) throws Exception {
            Thread t = new Thread(new Worker());
            t.start();
            Thread.sleep(100);
            t.interrupt();
            System.out.println("Main thread stopped.");
        }
    
        /**
         *  抛出一次InterruptedException异常后JVm会把线
         *  程的中断状态清除掉interrupt的状态设置为false
         */
        public static class Worker implements Runnable {
            public void run() {
                System.out.println("Worker started.");
                try {
                    Thread.sleep(200);
                } catch (InterruptedException e) {
                    System.out.println("抛出异常JVM会清除线程的中断状态    
                    Worker IsInterrupted--------: " +Thread.currentThread().isInterrupted());
                    // TODO: 2019/1/29 对抛出的InterruptedException的正确处理应该是设置
                      Thread.currentThread().interrupt(); 
                      Thread.currentThread().interrupt();
                     System.out.println("执行interrupt()设置线程的中断状态 Worker IsInterrupted--------: " 
                  +Thread.currentThread().isInterrupted());
                    /**
                     * 清除线程的中断状态
                     */
                    Thread.interrupted();
                    System.out.println("清除线程的中断状态interrupted();  Worker IsInterrupted--------: " +
                            Thread.currentThread().isInterrupted());
                }
                System.out.println("Worker stopped.");
    
            }
        }
    }
    
    

    当线程处于阻塞状态的时候,如果这是被interrupt,会报InterruptException错误,但是只是设置一个被打断的标志,不会正在的打断状态,所以要正确的处理exception,要加上Thread.currentThread().interrupt()方法,来中断线程。

    中断线程详解(Interrupt)

    官方文档中对此有详细说明:《为何不赞成使用 Thread.stop、Thread.suspend 和 Thread.resume?》。在此引用stop方法的说明:

    1. Why is Thread.stop deprecated?

    Because it is inherently unsafe. Stopping a thread causes it to unlock all the monitors that it has locked. (The monitors are unlocked as the ThreadDeath exception propagates up the stack.) If any of the objects previously protected by these monitors were in an inconsistent state, other threads may now view these objects in an inconsistent state. Such objects are said to be damaged. When threads operate on damaged objects, arbitrary behavior can result. This behavior may be subtle and difficult to detect, or it may be pronounced. Unlike other unchecked exceptions, ThreadDeath kills threads silently; thus, the user has no warning that his program may be corrupted. The corruption can manifest itself at any time after the actual damage occurs, even hours or days in the future.

    大概意思是:

    因为该方法本质上是不安全的。停止一个线程将释放它已经锁定的所有监视器(作为沿堆栈向上传播的未检查 ThreadDeath 异常的一个自然后果)。如果以前受这些监视器保护的任何对象都处于一种不一致的状态,则损坏的对象将对其他线程可见,这有可能导致任意的行为。此行为可能是微妙的,难以察觉,也可能是显著的。不像其他的未检查异常,ThreadDeath异常会在后台杀死线程,因此,用户并不会得到警告,提示他的程序可能已损坏。这种损坏有可能在实际破坏发生之后的任何时间表现出来,也有可能在多小时甚至在未来的很多天后。在此我向大家推荐一个架构学习交流圈。交流学习指导伪鑫:1253431195(里面有大量的面试题及答案)里面会分享一些资深架构师录制的视频录像:有Spring,MyBatis,Netty源码分析,高并发、高性能、分布式、微服务架构的原理,JVM性能优化、分布式架构等这些成为架构师必备的知识体系。还能领取免费的学习资源,目前受益良多

    在文档中还提到,程序员不能通过捕获ThreadDeath异常来修复已破坏的对象。具体原因见原文。

    既然stop方法不建议使用,那么应该用什么方法来代理stop已实现相应的功能呢?

    通过修改共享变量来通知目标线程停止运行

    大部分需要使用stop的地方应该使用这种方法来达到中断线程的目的。

    这种方法有几个要求或注意事项:

    • 目标线程必须有规律的检查变量,当该变量指示它应该停止运行时,该线程应该按一定的顺序从它执行的方法中返回。

    • 该变量必须定义为volatile,或者所有对它的访问必须同步(synchronized)。

    例如:

    假如你的applet包括start,stop,run几个方法:

     private Thread blinker; 
    
    public void start() { 
        blinker = new Thread(this); 
        blinker.start(); 
    } 
    
    public void stop() { 
        blinker.stop();  // UNSAFE! 
    } 
    
    public void run() { 
        Thread thisThread = Thread.currentThread(); 
        while (true) { 
            try { 
            thisThread.sleep(interval); 
            } catch (InterruptedException e){ 
            } 
            repaint(); 
        } 
    } 
    
    

    你可以使用如下方式避免使用Thread.stop方法:

    private volatile Thread blinker; 
    
    public void stop() { 
        blinker = null; 
    } 
    
    public void run() { 
        Thread thisThread = Thread.currentThread(); 
        while (blinker == thisThread) { 
            try { 
                thisThread.sleep(interval); 
            } catch (InterruptedException e){ 
            } 
            repaint(); 
        } 
    } 
    
    

    通过Thread.interrupt方法中断线程

    通常情况下,我们应该使用第一种方式来代替Thread.stop方法。然而以下几种方式应该使用Thread.interrupt方法来中断线程(该方法通常也会结合第一种方法使用)。

    一开始使用interrupt方法时,会有莫名奇妙的感觉:难道该方法有问题?

    API文档上说,该方法用于"Interrupts this thread"。请看下面的例子:

    package com.polaris.thread; 
    
    public class TestThread implements Runnable{ 
    
        boolean stop = false; 
        public static void main(String[] args) throws Exception { 
            Thread thread = new Thread(new TestThread(),"My Thread"); 
            System.out.println( "Starting thread..." ); 
            thread.start(); 
            Thread.sleep( 3000 ); 
            System.out.println( "Interrupting thread..." ); 
            thread.interrupt(); 
            System.out.println("线程是否中断:" + thread.isInterrupted()); 
            Thread.sleep( 3000 ); 
            System.out.println("Stopping application..." ); 
        } 
        public void run() { 
            while(!stop){ 
                System.out.println( "My Thread is running..." ); 
                // 让该循环持续一段时间,使上面的话打印次数少点 
                long time = System.currentTimeMillis(); 
                while((System.currentTimeMillis()-time < 1000)) { 
                } 
            } 
            System.out.println("My Thread exiting under request..." ); 
        } 
    } 
    
    

    运行后的结果是:
    Starting thread...
    My Thread is running...
    My Thread is running...
    My Thread is running...
    My Thread is running...
    Interrupting thread...
    线程是否中断:true
    My Thread is running...
    My Thread is running...
    My Thread is running...
    Stopping application...
    My Thread is running...
    My Thread is running...
    ……

    
    应用程序并不会退出,启动的线程没有因为调用interrupt而终止,可是从调用isInterrupted方法返回的结果可以清楚地知道该线程已经中断了。那位什么会出现这种情况呢?到底是interrupt方法出问题了还是isInterrupted方法出问题了?
    
    实际上,在JAVA API文档中对该方法进行了详细的说明。该方法实际上只是设置了一个中断状态,当该线程由于下列原因而受阻时,这个中断状态就起作用了:
    
    > **如果线程在调用 Object 类的 wait()、wait(long) 或 wait(long, int) 方法,或者该类的 join()、join(long)、join(long, int)、sleep(long) 或 sleep(long, int) 方法过程中受阻,则其中断状态将被清除,它还将收到一个InterruptedException异常**。
    
    >这个时候,我们可以通过捕获InterruptedException异常来终止线程的执行,具体可以通过return等退出或改变共享变量的值使其退出。
    
    **如果该线程在可中断的通道上的 I/O 操作中受阻,则该通道将被关闭,该线程的中断状态将被设置并且该线程将收到一个 ClosedByInterruptException。这时候处理方法一样,只是捕获的异常不一样而已。**
    
    其实对于这些情况有一个通用的处理方法:
    
    ```java
    package com.polaris.thread; 
    
    public class TestThread2 implements Runnable{ 
    
        boolean stop = false; 
        public static void main(String[] args) throws Exception { 
            Thread thread = new Thread(new TestThread2(),"My Thread2"); 
            System.out.println( "Starting thread..." ); 
            thread.start(); 
            Thread.sleep( 3000 ); 
            System.out.println( "Interrupting thread..." ); 
            thread.interrupt(); 
            System.out.println("线程是否中断:" + thread.isInterrupted()); 
            Thread.sleep( 3000 ); 
            System.out.println("Stopping application..." ); 
        } 
        public void run() { 
            while(!stop){ 
                System.out.println( "My Thread is running..." ); 
                // 让该循环持续一段时间,使上面的话打印次数少点 
                long time = System.currentTimeMillis(); 
                while((System.currentTimeMillis()-time < 1000)) { 
                } 
                if(Thread.currentThread().isInterrupted()) { 
                    return; 
                } 
            } 
            System.out.println("My Thread exiting under request..." ); 
        } 
    } 
    
    

    调用interrupt方法后,会设置线程的中断状态,所以,通过监视该状态来达到终止线程的目的。

    总结:程序应该对线程中断作出恰当的响应。响应方式通常有三种:

    image

    相关文章

      网友评论

          本文标题:【Java进阶营】Java技术专题-线程Interrupt实现机

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