zoukankan      html  css  js  c++  java
  • 如何中止Java中的线程

    如何正确的中止一个运行中的线程

    Java作为第一款官方声明支持多线程的编程语言,其早期提供的一些Api并不是特别的完善,所以可以看到Thread类中的一些早期方法都已经被标记上过时了,例如stop、resume,suspend,destory方法都被标记上过时的标签。那为了弥补这些缺失的功能,后续的Java提供了一些额外的方法,例如interrupt这样的方法。
    stop方法会立即中止线程的运行并抛出错误,同时释放所有的锁,这可能产生数据安全问题(https://docs.oracle.com/javase/8/docs/technotes/guides/concurrency/threadPrimitiveDeprecation.html)。interrupt方法的实现方式更类似于给线程发送了一个信号(简单的理解为给线程设置了一个属性来标记是否应当停止当前线程,默认为false),线程接收到信号之后具体如何处理,是中止线程还是正常运行取决于运行的线程的具体逻辑。

    中止线程的方法大致分两种

    • 通过Thread.interrupt()
    • 通过线程间的共享标记变量

    通过interrupt()来中止一个线程

    由于被中止的线程可能处于不同的状态,被终止的线程需要自行检测是否有人发信号给他,通知应该停止工作。

    检测是否有人发信号的方法有两种

    • Thread.currentThread().isInterrupted() 返回当前线程上的标识位。推荐使用,该方法调用后不会修改标识;
    • Thread.interrupted() 返回当前线程上的标识位,同时调用后会立即把标识位修改位false。

    终止一个runnable线程的最佳实践:

    while(!Thread.currentThread().isInterrupted() && more work to do){
        do more work
    }
    

    处于runnable状态的线程被interrupt

    看一个例子,对于一个循环打印running的线程,我们在main线程中调用了interrupt()方法,但是线程并没有按照我们预想的那样停下来,这样的代码并不是按照最佳实践的方式来运行的,我们没有把线程是否有接受到interrupt的信号放到while的判断去。

    public class InterruptRunning {
        public static void main(String[] args) {
            Thread thread = new Thread(() -> {
                while (true) {
                    System.out.println("running");
                }
            });
    
            thread.start();
            try {
                Thread.sleep(2);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            thread.interrupt();
            System.out.println("Test End");
        }
    }
    //输出结果
    //一直持续不断的输出running ,并没有像期待的那样2ms之后输出Test End
    

    以上的代码的正确写法应该是,可以看到结果是我们期待的

    public class InterruptRunning {
        public static void main(String[] args) {
            Thread thread = new Thread(() -> {
                while (!Thread.currentThread().isInterrupted()) {
                    System.out.println("running");
                }
            });
    
            thread.start();
            try {
                Thread.sleep(2);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            thread.interrupt();
            System.out.println("Test End");
        }
    }
    //输出结果
    running
    running
    running
    running
    running
    running
    running
    running
    Test End
    

    处于blocked/waiting/timed waiting状态的线程被interrupt

    如果一个线程处于了阻塞状态(如线程调用了thread.sleep、thread.join、thread.wait、1.5中的condition.await、以及可中断的通道上的 I/O 操作方法后可进入阻塞状态),则在线程在检查中断标示时如果发现中断标示为true,则会在这些阻塞方法(sleep、join、wait、1.5中的condition.await及可中断的通道上的 I/O 操作方法)调用处抛出InterruptedException异常,并且在抛出异常后立即将线程的中断标示位清除,即重新设置为false。抛出异常是为了线程从阻塞状态醒过来,并在结束线程前让程序员有足够的时间来处理中断请求。

    即先后做了三件事

    • 使线程状态由阻塞状态变为runnable
    • 抛出InterruptedException异常
    • 如果InterruptedException异常被捕获,会立即将中断标识由true设置为false

    举个例子,我们在循环的时候通过中断标识位来决定是否循环,并且将try catch代码块置于while循环内部。此时中断产生的异常会先被try catch捕获,然后中断标识位会被设置位false;while循环不会中止,线程依旧执行。
    在main线程中调用standardInterrupt.interrupt();尝试中止中止StandardInterrupt线程,线程的输出内容说明了其捕获到了异常,但是检查标识发现值位false说明已经被重置了,于是可以看到while循环条件满足,线程不会停止,依旧运行下去。

    public class StandardInterrupt extends Thread{
        public static void main(String[] args) throws InterruptedException {
            StandardInterrupt standardInterrupt = new StandardInterrupt();
            System.out.println("main thread --> start running");
            standardInterrupt.start();
            Thread.sleep(3000);
            System.out.println("main thread --> send signal");
            standardInterrupt.interrupt();
            Thread.sleep(3000);
            System.out.println("main thread --> stop application");
        }
    
        public void run(){
            while(!Thread.currentThread().isInterrupted()){
                System.out.println("active thread --> i am working");
                try {
                    Thread.sleep(1000);
                }catch (InterruptedException e){
                    System.out.println("active thread --> detective interrupt");
                    System.out.println("active thread --> check the signal: " +Thread.currentThread().isInterrupted());
                }
            }
            System.out.println("active thread --> finish my working");
        }
    }
    
    //输出
    main thread --> start running
    active thread --> i am working
    active thread --> i am working
    active thread --> i am working
    main thread --> send signal
    active thread --> detective interrupt
    active thread --> check the signal: false
    main thread --> stop application
    active thread --> i am working
    active thread --> i am working
    active thread --> i am working
    ......
    

    我们再稍作修改如上的代码,在捕获异常之后,主动的再次触发一次interrupt(),这可以将被自动设置回false的中断标识位再次变为true。
    这次可以看到active thread --> finish my working输出,因为while循环去检查标识的时候,Thread.currentThread().isInterrupted() = true;

    public class StandardInterrupt extends Thread{
        public static void main(String[] args) throws InterruptedException {
            StandardInterrupt standardInterrupt = new StandardInterrupt();
            System.out.println("main thread --> start running");
            standardInterrupt.start();
            Thread.sleep(3000);
            System.out.println("main thread --> send signal");
            standardInterrupt.interrupt();
            Thread.sleep(3000);
            System.out.println("main thread --> stop application");
        }
    
        public void run(){
            while(!Thread.currentThread().isInterrupted()){
                System.out.println("active thread --> i am working");
                try {
                    Thread.sleep(1000);
                }catch (InterruptedException e){
                    System.out.println("active thread --> detective interrupt");
                    System.out.println("active thread --> check the signal: " +Thread.currentThread().isInterrupted());
                    Thread.currentThread().interrupt();
                }
            }
            System.out.println("active thread --> finish my working");
        }
    }
    
    //输出
    main thread --> start running
    active thread --> i am working
    active thread --> i am working
    active thread --> i am working
    main thread --> send signal
    active thread --> detective interrupt
    active thread --> check the signal: false
    active thread --> finish my working
    main thread --> stop application
    ......
    

    另一种修改方式,是将try catch放在while循环外面,这样跳出while的原因是因为检测到异常。然后try catch捕获异常,修改标识位。

    public class StandardInterrupt extends Thread {
        public static void main(String[] args) {
            StandardInterrupt standardInterrupt = new StandardInterrupt();
            System.out.println("main thread --> start running");
            standardInterrupt.start();
            try {
                Thread.sleep(3000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println("main thread --> send signal");
            standardInterrupt.interrupt();
            try {
                Thread.sleep(3000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println("main thread --> stop application");
        }
    
        public void run() {
            try {
                while (!Thread.currentThread().isInterrupted()) {
                    System.out.println("active thread --> i am working");
                    Thread.sleep(1000);
                }
            } catch (InterruptedException e) {
                System.out.println("active thread --> detective interrupt");
                System.out.println("active thread --> check the signal: " + Thread.currentThread().isInterrupted());
            }
            System.out.println("active thread --> finish my working");
        }
    }
    
    //输出
    main thread --> start running
    active thread --> i am working
    active thread --> i am working
    active thread --> i am working
    main thread --> send signal
    active thread --> detective interrupt
    active thread --> check the signal: false
    active thread --> finish my working
    main thread --> stop application
    ......
    

    总结一下对于要处理这种blocked状态的线程应该正确书写代码的格式

    public void run() {
        try {
            ...
            /*
             * 不管循环里是否调用过线程阻塞的方法如sleep、join、wait,这里还是需要加上
             * !Thread.currentThread().isInterrupted()条件,虽然抛出异常后退出了循环,显
             * 得用阻塞的情况下是多余的,但如果调用了阻塞方法但没有阻塞时,这样会更安全、更及时。
             */
            while (!Thread.currentThread().isInterrupted()&& more work to do) {
                do more work 
            }
        } catch (InterruptedException e) {
            //线程在wait或sleep期间被中断了
        } finally {
            //线程结束前做一些清理工作
        }
    }
    

    或者

    public void run() {
        while (!Thread.currentThread().isInterrupted()&& more work to do) {
            try {
                ...
                sleep(delay);
            } catch (InterruptedException e) {
                Thread.currentThread().interrupt();//重新设置中断标示
            }
        }
    }
    

    处于blocked状态的线程

    • 由synchronized,或者是由reentrantLock.lock()导致的锁是无法响应interrupt()方法的,他们只有在获取锁之后正常运行的时候才能响应。
    • reentrantLock.tryLock(long timeout, TimeUnit unit),reentrantLock.lockInterruptibly()方法所导致的blocked状态却是能正确响应interrupt()方法的,锁住的线程将抛出一个InterruptedException异常,这是一个非常有用的特性,因为它允许程序打破死锁。
  • 相关阅读:
    气泡框箭头制作
    字体图标
    JQ 1.9 API在线资源
    JS获取屏幕,浏览器窗口大小,网页高度宽度(实现代码)_javascript技巧_
    canvas如何自适应屏幕大小
    System.Type.cs
    System.Security.Policy.EvidenceBase.cs
    System.Security.Policy.Evidence.cs
    System.Security.IEvidenceFactory.cs
    AIX-vi操作-提示Unknown terminal type的问题解决方法
  • 原文地址:https://www.cnblogs.com/Pikzas/p/13725821.html
Copyright © 2011-2022 走看看