• 多线程(四) 如何停止线程


      在Thread类中提供了可以停止线程的方法(包括杀死和挂起):

        @Deprecated
        public final void stop(){}

        @Deprecated
        public final void suspend(){}
        

       stop 和 suspend 添加的有Deprecated注释,也即是该方法已经废弃使用。那么为什么会不建议使用这两种方法呢?还有没有其他停止线程的方法?

    1、stop()会立即杀死线程,无法保证原子操作能够顺利完成,存在数据错误的风险。无论线程执行到哪里、是否加锁,stop会立即停止线程运行,直接退出。

       如下代码:

     int account01 = 10;
        int account02= 0;
        Object lock = new Object();
        
        public void testStop() {
            class StopRunnable implements Runnable {
                @Override
                public void run() {
                    //要求 account01 + account02 =10  恒成立
                    while (true) {
                        synchronized (lock) {//加锁保证操作的原子性
                            account01--;
                            sleep(200);//睡眠模拟执行过程
                            account02++;
                        }
                    }
                }
            }
            Thread thread = new Thread(new StopRunnable());
            thread.start();
            sleep(1300);
            thread.stop();
            System.out.println("account01: " + account01 + "
    account02: " + account02);
        }
        private void sleep(int time){
            try {
                Thread.sleep(time);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }

     运行结果如下:

    account01: 3
    account02: 6

     很明显没有保证两者的和为10。说明在线程循环过程中,最后一个加锁循环体没有完整执行结束,数据发生错误。除此之外,在执行stop方法之后,线程会立即释放锁,这同样也会导致原子操作失败数据异常。

    官方注释:

    Forces the thread to stop executing.
    It is permitted to stop a thread that has not yet been started.
    If the thread is eventually started, it immediately terminates.

     2、suspend()并未杀死线程,只是把线程挂起,停止线程的运行。但挂起之后并不会释放锁,这样,如果有其它多个线程在等待该锁,则程序将会发生死锁。

      如下代码:

        int account01 = 10;
        int account02= 0;
        Object lock = new Object();
    
        public void testStop() {
            class StopRunnable implements Runnable {
                @Override
                public void run() {
                    //要求 account01 + account02 =10  恒成立
                    for(int i =0;i<5;i++){
                        synchronized (lock) {//加锁保证操作的原子性
                            account01--;
                            System.out.println("....."+Thread.currentThread().getName());//为了看到线程停止添加输出线程名称操作
                            sleep(200);//睡眠200ms
                            account02++;
                        }
                    }
                }
            }
            Thread thread01 = new Thread(new StopRunnable());
            thread01.setName("thread01");
            Thread thread02 = new Thread(new StopRunnable());
            thread02.setName("thread02");
    
            thread01.start();
            thread02.start();
    
            sleep(500);
            thread01.suspend();
            while (true){
                sleep(1000);
                System.out.println("account01: " + account01 + " account02: " + account02+" thread01 isAlive:"+thread01.isAlive()+" thread02 isAlive:"+thread02.isAlive());
            }
        }
    

    运行结果如下:  

    .....thread01
    .....thread01
    .....thread01
    account01: 7 account02: 2 thread01 isAlive:true thread02 isAlive:true
    account01: 7 account02: 2 thread01 isAlive:true thread02 isAlive:true
    account01: 7 account02: 2 thread01 isAlive:true thread02 isAlive:true
    account01: 7 account02: 2 thread01 isAlive:true thread02 isAlive:true
    account01: 7 account02: 2 thread01 isAlive:true thread02 isAlive:true
    account01: 7 account02: 2 thread01 isAlive:true thread02 isAlive:true

    由结果可以看出,thread01一直在运行,而thread02一次也没有执行到run方法。然后在执行thread01.suspend()之后,两个线程都停止了运行run方法。但同时两个线程都没有死亡。

    在代码中只对thread01执行了suspend,那么如果thread02获取到锁则应该继续由thread02执行run方法,但并没有,说明锁lock一直由thread01持有,在挂起之后并未释放。

    其实在使用suspend()方法的时候是需要配合resume()同时使用的。

         ....
         ....
    sleep(
    500); thread01.suspend(); int time = 0;//添加time计数,在循环三次之后释放 while (true){ time ++; if(time ==3){ thread01.resume();//释放线程 } sleep(1000); System.out.println("account01: " + account01 + " account02: " + account02+" thread01 isAlive:"+thread01.isAlive()+" thread02 isAlive:"+thread02.isAlive()); }

     执行结果如下:  

    .....thread01
    .....thread01
    .....thread01
    account01: 7 account02: 2 thread01 isAlive:true thread02 isAlive:true
    account01: 7 account02: 2 thread01 isAlive:true thread02 isAlive:true
    .....thread01  //释放之后继续运行
    .....thread01
    .....thread02  //thread01释放锁,thread02获取锁继续运行
    .....thread02
    .....thread02
    account01: 2 account02: 7 thread01 isAlive:false thread02 isAlive:true  //thread01 死亡,thread02活着
    .....thread02
    .....thread02
    account01: 0 account02: 10 thread01 isAlive:false thread02 isAlive:false
    account01: 0 account02: 10 thread01 isAlive:false thread02 isAlive:false
    account01: 0 account02: 10 thread01 isAlive:false thread02 isAlive:false
    account01: 0 account02: 10 thread01 isAlive:false thread02 isAlive:false

     可以看出,thread01.resume()之后thread01继续运行,然后运行结束释放锁之后,thread02接着运行起来,这时可以看到thread01已经死亡,而thread02依旧活着。直至两个线程全部结束。如果正常使用suspend()和resume()并不会出现太大问题,只是在涉及到锁的时候久需要格外小心了。r如果没有使用到锁,那么其中一个线程的挂起并不会影响到其他线程的执行。

    对于public void interrupt(){}方法,该方法只是对阻塞状态的线程(seleep、wait、join和IO/NIO操作)进行中断操作,在调用interrupt方法的时候,如果线程处于阻塞状态则会抛出InterruptedException/ClosedByInterruptException。在程序中只需对异常进行捕获,并不影响后续的操作。对于未处于阻塞状态的线程,调用interrupt方法没有任何影响。所以interrupt严格意义上说并不属于停止线程的方法。

    那么,到底该如何安全的停止线程呢?

      遵循的规则:让线程自己停止自己

         两种方法:1、线程任务执行完成,顺利结束退出。2、设置终止标志位,在循环的时候进行终止标志位检测,如果设置为终止状态则return结束线程。

     例如:1、线程执行完成自动退出:

                public void run() {
                    for(int i = 0;i<10;i++){//循环十次之后run方法结束自动结束线程
                        System.out.println(Thread.currentThread().getName());
                    }
                }

     2、设置终止标志位。

        boolean isStop = false;//终止标志位 当需要结束线程时,更改为true
        public void testInterrupt(){
            Thread th = new Thread(new Runnable() {
                @Override
                public void run() {
                    while(true){
                        if(isStop){//当需要结束线程的时候终止线程
                            //doSomething  进行一些收尾工作
                            return;
                        }
                        System.out.println(Thread.currentThread().getName());
                    }
                }
            });

      设置终止标志位的时候线程不会立即终止,只有当循环到标志位判断的时候才会执行退出操作,这样就可以在循环体中合适的地方执行退出逻辑,可以保证原子操作的顺利完成以及锁的释放。

     对于ExecutorService的void shutdown();方法,该方法只是停止线程池接受新的任务同时等待已提交线程结束,并不会停止线程。所以该方法不属于停止线程的方法。

    =========================================

    原文链接:多线程(四) 如何停止线程 转载请注明出处!

    =========================================

    ---end

  • 相关阅读:
    MSSQL
    Dapper
    Asynchronous Programming
    PHP on CentOS (LAMP) and wordpress
    CSS
    TypeScript & JavaScript
    AngularJS 2.0
    ReadMe.md MarkDown file
    shortcuts on Windows and MacOS
    移动平台的meta标签
  • 原文地址:https://www.cnblogs.com/PerkinsZhu/p/7350941.html
Copyright © 2020-2023  润新知