1. 程式人生 > >多線程(四) 如何停止線程

多線程(四) 如何停止線程

edi som set single ces 問題 ide 線程 nat

  在Thread類中提供了可以停止線程的方法(包括殺死和掛起):

[email protected]
    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();方法,該方法只是停止線程池接受新的任務同時等待已提交線程結束,並不會停止線程。所以該方法不屬於停止線程的方法。

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

原文鏈接:多線程(四) 如何停止線程 轉載請註明出處!

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

---end

多線程(四) 如何停止線程