1. 程式人生 > >多執行緒(四) 如何停止執行緒

多執行緒(四) 如何停止執行緒

在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 + "\naccount02: " + 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();方法,該方法只是停止執行緒池接受新的任務同時等待已提交執行緒結束,並不會停止執行緒。所以該方法不屬於停止執行緒的方法。

 

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

 

原文連結:多執行緒(四) 如何停止執行緒 轉載請註明出處!

 

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