(轉)Java結束線程的三種方法


背景:面試過程中問到結束線程的方法和線程池shutdown shutdownnow區別以及底層的實現,當時答的並不好。

Java結束線程的三種方法

線程屬於一次性消耗品,在執行完run()方法之后線程便會正常結束了,線程結束后便會銷毀,不能再次start,只能重新建立新的線程對象,但有時run()方法是永遠不會結束的。例如在程序中使用線程進行Socket監聽請求,或是其他的需要循環處理的任務。在這種情況下,一般是將這些任務放在一個循環中,如while循環。當需要結束線程時,如何退出線程呢?

有三種方法可以結束線程:

  • 1.設置退出標志,使線程正常退出,也就是當run()方法完成后線程終止

  • 2.使用interrupt()方法中斷線程

  • 3.使用stop方法強行終止線程(不推薦使用,Thread.stop, Thread.suspend, Thread.resume 和Runtime.runFinalizersOnExit 這些終止線程運行的方法已經被廢棄,使用它們是極端不安全的!)

前兩種方法都可以實現線程的正常退出;第3種方法相當於電腦斷電關機一樣,是不安全的方法

1.使用退出標志終止線程 
一般run()方法執行完,線程就會正常結束,然而,常常有些線程是伺服線程。它們需要長時間的運行,只有在外部某些條件滿足的情況下,才能關閉這些線程。使用一個變量來控制循環,例如:最直接的方法就是設一個boolean類型的標志,並通過設置這個標志為true或false來控制while循環是否退出,代碼示例:

public class ThreadSafe extends Thread {
    public volatile boolean exit = false; 
        public void run() { 
        while (!exit){
            //do something
        }
    } 
}

 

定義了一個退出標志exit,當exit為true時,while循環退出,exit的默認值為false.在定義exit時,使用了一個Java關鍵字volatile,這個關鍵字的目的是使exit同步,也就是說在同一時刻只能由一個線程來修改exit的值.

2.使用interrupt()方法中斷當前線程 
使用interrupt()方法來中斷線程有兩種情況:

1.線程處於阻塞狀態,如使用了sleep,同步鎖的wait,socket中的receiver,accept等方法時,會使線程處於阻塞狀態。當調用線程的interrupt()方法時,會拋出InterruptException異常。阻塞中的那個方法拋出這個異常,通過代碼捕獲該異常,然后break跳出循環狀態,從而讓我們有機會結束這個線程的執行。通常很多人認為只要調用interrupt方法線程就會結束,實際上是錯的, 一定要先捕獲InterruptedException異常之后通過break來跳出循環,才能正常結束run方法。

代碼示例:

public class ThreadSafe extends Thread {
    public void run() { 
        while (true){
            try{
                    Thread.sleep(5*1000);//阻塞5妙
                }catch(InterruptedException e){
                    e.printStackTrace();
                    break;//捕獲到異常之后,執行break跳出循環。
                }
        }
    } 
}

2.線程未處於阻塞狀態,使用isInterrupted()判斷線程的中斷標志來退出循環。當使用interrupt()方法時,中斷標志就會置true,和使用自定義的標志來控制循環是一樣的道理。 
代碼示例:

public class ThreadSafe extends Thread {
    public void run() { 
        while (!isInterrupted()){
            //do something, but no throw InterruptedException
        }
    } 
}

為什么要區分進入阻塞狀態和和非阻塞狀態兩種情況了,是因為當阻塞狀態時,如果有interrupt()發生,系統除了會拋出InterruptedException異常外,還會調用interrupted()函數,調用時能獲取到中斷狀態是true的狀態,調用完之后會復位中斷狀態為false,所以異常拋出之后通過isInterrupted()是獲取不到中斷狀態是true的狀態,從而不能退出循環,因此在線程未進入阻塞的代碼段時是可以通過isInterrupted()來判斷中斷是否發生來控制循環,在進入阻塞狀態后要通過捕獲異常來退出循環。因此使用interrupt()來退出線程的最好的方式應該是兩種情況都要考慮:

代碼示例:

public class ThreadSafe extends Thread {
    public void run() { 
        while (!isInterrupted()){ //非阻塞過程中通過判斷中斷標志來退出
            try{
                Thread.sleep(5*1000);//阻塞過程捕獲中斷異常來退出
            }catch(InterruptedException e){
                e.printStackTrace();
                break;//捕獲到異常之后,執行break跳出循環。
            }
        }
    } 
}

3.使用stop方法終止線程 
程序中可以直接使用thread.stop()來強行終止線程,但是stop方法是很危險的,就象突然關閉計算機電源,而不是按正常程序關機一樣,可能會產生不可預料的結果,不安全主要是:thread.stop()調用之后,創建子線程的線程就會拋出ThreadDeatherror的錯誤,並且會釋放子線程所持有的所有鎖。一般任何進行加鎖的代碼塊,都是為了保護數據的一致性,如果在調用thread.stop()后導致了該線程所持有的所有鎖的突然釋放(不可控制),那么被保護數據就有可能呈現不一致性,其他線程在使用這些被破壞的數據時,有可能導致一些很奇怪的應用程序錯誤。因此,並不推薦使用stop方法來終止線程。

 

interrupt、interrupted 、isInterrupted 區別

 

interrupt、interrupted 、isInterrupted 區別

1、interrupt 

interrupt方法用於中斷線程。調 用該方法的線程的狀態為將被置為"中斷"狀態。
注意: 線程中斷僅僅是置線程的中斷狀態位,不會停止線程。需要用戶自己去監視線程的狀態為並做處理。支持線程中斷的方法(也就是線程中斷后會拋出interruptedException的方法)就是在監視線程的中斷狀態,一旦線程的中斷狀態被置為“中斷狀態”,就會拋出中斷異常。
    public void interrupt() {
        if (this != Thread.currentThread())
            checkAccess();

        synchronized (blockerLock) {
            Interruptible b = blocker;
            if (b != null) {
                interrupt0();           // Just to set the interrupt flag
                b.interrupt(this);
                return;
            }
        }
        interrupt0();
    }

    private native void interrupt0();

 

interrupt 設置中斷狀態。

2、interrupted 和 isInterrupted

首先看一下該方法的實現:
    public static boolean interrupted() {
        return currentThread().isInterrupted(true);
    }
該方法就是直接調用當前線程的isInterrupted(true)方法。
靜態方法,返回當前的線程的狀態,並會清楚之前的狀態。
然后再來看一下 isInterrupted的實現:
    public boolean isInterrupted() {
        return isInterrupted(false);
    }

只會返回當前線程的狀態,並不會清除。 

底層都是調用一個本地方法:

    /**
     * Tests if some Thread has been interrupted.  The interrupted state
     * is reset or not based on the value of ClearInterrupted that is
     * passed.
     */
    private native boolean isInterrupted(boolean ClearInterrupted);

 

原來這是一個本地方法,看不到源碼。不過沒關系,通過參數名我們就能知道,這個參數代表是否要清除狀態位。

如果這個參數為true,說明返回線程的狀態位后,要清掉原來的狀態位(恢復成原來情況)。這個參數為false,就是直接返回線程的狀態位。

這兩個方法很好區分,只有當前線程才能清除自己的中斷位(對應interrupted()方法)

 

interrupted 和 isInterrupted

這兩個方法有兩個主要區別:
  1. interrupted 是作用於當前線程,isInterrupted 是作用於調用該方法的線程對象所對應的線程。(線程對象對應的線程不一定是當前運行的線程。例如我們可以在A線程中去調用B線程對象的isInterrupted方法。)
  2. 這兩個方法最終都會調用同一個方法,只不過參數一個是true,一個是false;

是不是這樣理解呢,
Thread.currentThread().interrupted(); 這個用於清除中斷狀態,這樣下次調用Thread.interrupted()方法時就會一直返回為false,因為中斷標志已經被恢復了。
而調用isInterrupted 只是簡單的查詢中斷狀態,不會對狀態進行修改。

interrupt()是用來設置中斷狀態的。返回true說明中斷狀態被設置了而不是被清除了。我們調用sleep、wait等此類可中斷(throw InterruptedException)方法時,一旦方法拋出InterruptedException,當前調用該方法的線程的中斷狀態就會被jvm自動清除了,就是說我們調用該線程的isInterrupted 方法時是返回false。如果你想保持中斷狀態,可以再次調用interrupt方法設置中斷狀態。這樣做的原因是,java的中斷並不是真正的中斷線程,而只設置標志位(中斷位)來通知用戶。如果你捕獲到中斷異常,說明當前線程已經被中斷,不需要繼續保持中斷位。
interrupted是靜態方法,返回的是當前線程的中斷狀態。例如,如果當前線程被中斷(沒有拋出中斷異常,否則中斷狀態就會被清除),你調用interrupted方法,第一次會返回true。然后,當前線程的中斷狀態被方法內部清除了。第二次調用時就會返回false。如果你剛開始一直調用isInterrupted,則會一直返回true,除非中間線程的中斷狀態被其他操作清除了。

 

https://github.com/CyC2018/CS-Notes/blob/master/docs/notes/Java%20%E5%B9%B6%E5%8F%91.md#interrupted

ps:里面有interrupted()相關的詳細使用例子。

 

調用 Executor 的 shutdown() 方法會等待線程都執行完畢之后再關閉,但是如果調用的是 shutdownNow() 方法,則相當於調用每個線程的 interrupt() 方法。

如果只想中斷 Executor 中的一個線程,可以通過使用 submit() 方法來提交一個線程,它會返回一個 Future<?> 對象,通過調用該對象的 cancel(true) 方法就可以中斷線程。

Future<?> future = executorService.submit(() -> {
    // ..
});
future.cancel(true);

threadPoolExecutor 中的 shutdown() 、 shutdownNow() 、 awaitTermination() 的用法和區別

threadPoolExecutor 中的 shutdown() 、 shutdownNow() 、 awaitTermination() 的用法和區別

最近在看並發編程,在使用到ThreadPoolExecutor時,對它的三個關閉方法(shutdown()、shutdownNow()、awaitTermination())產生了興趣,同時又感到迷惑。查了些資料,自己寫了測試代碼,總算有了個比較清晰的認識。下面一起來看看這三個方法:

shutdown()

將線程池狀態置為SHUTDOWN,並不會立即停止:

  • 停止接收外部submit的任務
  • 內部正在跑的任務和隊列里等待的任務,會執行完
  • 等到第二步完成后,才真正停止

shutdownNow()

線程池狀態置為STOP。企圖立即停止,事實上不一定:

跟shutdown()一樣,

  • 先停止接收外部提交的任務
  • 忽略隊列里等待的任務
  • 嘗試將正在跑的任務interrupt中斷
  • 返回未執行的任務列表

它試圖終止線程的方法是通過調用Thread.interrupt()方法來實現的,但是大家知道,這種方法的作用有限,如果線程中沒有sleep 、wait、Condition、定時鎖等應用, interrupt()方法是無法中斷當前的線程的。所以,ShutdownNow()並不代表線程池就一定立即就能退出,它也可能必須要等待所有正在執行的任務都執行完成了才能退出。

但是大多數時候是能立即退出的

awaitTermination(long timeOut, TimeUnit unit)

當前線程阻塞,直到

  • 等所有已提交的任務(包括正在跑的和隊列中等待的)執行完
  • 或者等超時時間到
  • 或者線程被中斷,拋出InterruptedException
  • 然后返回true(shutdown請求后所有任務執行完畢)或false(已超時)

實驗發現,shuntdown()和awaitTermination()效果差不多,方法執行之后,都要等到提交的任務全部執行完才停。

shutdown()和shutdownNow()的區別

從字面意思就能理解,shutdownNow()能立即停止線程池,正在跑的和正在等待的任務都停下了。這樣做立即生效,但是風險也比較大;
shutdown()只是關閉了提交通道,用submit()是無效的;而內部該怎么跑還是怎么跑,跑完再停。

Between client threads and thread pool there is a queue of tasks. When your application shuts down, you must take care of two things: what is happening with queued tasks and how already running tasks are behaving (more on that later). Surprisingly many developers are not shutting down thread pool properly or consciously. There are two techniques: either let all queued tasks to execute (shutdown()) or drop them (shutdownNow()) - it totally depends on your use case.

shutdown()和awaitTermination()的區別
shutdown()后,不能再提交新的任務進去;但是awaitTermination()后,可以繼續提交。
awaitTermination()是阻塞的,返回結果是線程池是否已停止(true/false);shutdown()不阻塞。

總結

優雅的關閉,用shutdown()
想立馬關閉,並得到未執行任務列表,用shutdownNow()
優雅的關閉,並允許關閉聲明后新任務能提交,用awaitTermination()
關閉功能 【從強到弱】 依次是:shuntdownNow() > shutdown() > awaitTermination()

線程池的狀態

Java多線程線程池(4)--線程池的五種狀態

線程池的5種狀態:Running、ShutDown、Stop、Tidying、Terminated

線程池各個狀態切換框架圖:

image

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM