您現在的位置是:首頁 > 綜合

面試官:執行緒如何啟動和優雅的終止

  • 由 架構師半仙 發表于 綜合
  • 2022-01-20
簡介java:748)work-thread interrupt is truesleeper-thread interrupt is false當中斷阻塞狀態的sleeper執行緒,立馬丟擲了InterruptedException異常,重置

如何正確關閉一個執行緒

本節講解的內容是執行緒的初始化、執行緒啟動、執行緒中斷、suspend()、resume()、stop()和優雅的關閉執行緒。

1。執行緒初始化和執行緒的啟動

首先看下Thread的構造方法:

public Thread(Runnable target, String name) { init(null, target, name, 0); }

當然Thread有多個構造方法,都是呼叫init方法初始化一個執行緒。看下init的定義:

private void init(ThreadGroup g, Runnable target, String name, long stackSize, AccessControlContext acc, boolean inheritThreadLocals) { 。。。 }

包含了所屬執行緒組、Runable物件、執行緒名稱、執行緒的棧大小、訪問控制權和inheritThreadLocals。當我們透過new關鍵字建立執行緒的時候,預設由當前執行緒來進行空間分配的,而創建出來的執行緒繼承了當前執行緒的是否為守護執行緒、執行緒優先順序、contextClassLoader以及ThreadLocal。建立好的執行緒物件初始化完成後,在堆記憶體中等待執行。

注意,建立執行緒時最好執行執行緒的名字,方便透過命令jstack分析堆疊資訊和問題排查。如果不手動指定執行緒名稱,預設是Thread-“ + nextThreadNum()。

執行緒啟動是呼叫start()方法,當前執行緒同步告訴Java虛擬機器,如果執行緒規劃器空閒,應該立即啟動執行緒。start方法不可以重複呼叫。

面試中經常會問到直接執行run()方法會怎麼?

首先run方法只是個普通方法,直接執行不會啟動一個執行緒,且當前只有一個主執行緒(當前執行緒),程式順序執行,達不到多執行緒的目的。且run方法可以重複執行。

2。執行緒的中斷

當我們執行一個阻塞IO或者長時間沒有響應的任務時,我們需要結束執行緒以避免長時間等待。Thread。interrupt(),不是立馬中斷執行緒,而是設定一箇中斷標識位。Java中的阻塞函式比如Thread。sleep,Object。wait,Thread。join等,會不斷地輪訓檢測執行緒中斷標識位是否為true,如果為true,會立馬丟擲InterruptedException,同時把標識位設定為false。在下面的程式中,有兩個執行緒,worker和sleeper,執行緒中斷後,觀察下輸出的標識位:

public class TestInterrupt { public static void main(String[] args) { Thread worker = new Thread(()->{ while (true){} }, ”work-thread“); Thread sleeper = new Thread(()->{ while (true){ try { Thread。sleep(100); } catch (InterruptedException e) { e。printStackTrace(); } } }, ”sleeper-thread“); worker。start(); sleeper。start(); worker。interrupt(); sleeper。interrupt(); System。out。println(”work-thread interrupt is “ + worker。isInterrupted()); System。out。println(”sleeper-thread interrupt is “ + sleeper。isInterrupted()); }}

java。lang。InterruptedException: sleep interrupted at java。lang。Thread。sleep(Native Method) at net。zhifou。concurrent。base。TestInterrupt。lambda$main$1(TestInterrupt。java:16) at java。lang。Thread。run(Thread。java:748)work-thread interrupt is truesleeper-thread interrupt is false

當中斷阻塞狀態的sleeper執行緒,立馬丟擲了InterruptedException異常,重置中斷標識位後輸出中斷標識位為false,worker執行緒中斷標識位仍為true。

Thread的兩個方法,interrupted和isInterrupted,都是返回中斷標識位。我們先看下方法定義:

public static boolean interrupted() { return currentThread()。isInterrupted(true); } public boolean isInterrupted() { return isInterrupted(false); }

然後再看下isInterrupted方法的實現:

/** * 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);

這個是native方法,看不到實現沒關係,看下注釋,根據ClearInterrupted的值重置或不重置。isInterrupted傳入的是false,不清除中斷的標識位。而interrupted,傳入的是ture,則返回中斷標識位後,然後清除中斷標識位。

3。 suspend()、resume()、stop()

suspend()、resume()、stop()這三個方法,分別對應著,執行緒暫停、執行緒恢復、執行緒停止。觀察Java原始碼,都被標識為@Deprecated,不建議使用了。

首先我們看suspend()、resume(),它們必須成對出現,且有先後順序。那麼為什麼會被廢棄呢?

因為執行緒呼叫suspend程式暫停後,不會釋放任何資源,包括鎖,且一直處於睡眠狀態,容易引發死鎖。而resume因為要和suspend成對出現,所以也沒有存在的必要。

那麼stop方法為什麼不建議使用呢?我們先看原始碼:

@Deprecated public final void stop() { SecurityManager security = System。getSecurityManager(); if (security != null) { checkAccess(); if (this != Thread。currentThread()) { security。checkPermission(SecurityConstants。STOP_THREAD_PERMISSION); } } // A zero status value corresponds to ”NEW“, it can‘t change to // not-NEW because we hold the lock。 if (threadStatus != 0) { resume(); // Wake up thread if it was suspended; no-op otherwise } // The VM can handle all thread states stop0(new ThreadDeath()); }

最終透過呼叫stop0(new ThreadDeath())函式,stop0是Native函式,引數是ThreadDeath,ThreadDeath是一個異常物件,該物件從Native層拋到了Java層。執行緒執行中遇到異常,會導致執行緒停止,不過不會引起程式退出。

執行緒是因為異常而停止的,鎖會釋放,但是不會釋放其他的資源,比如開啟的socket連線或者IO,多不會關閉,引發記憶體洩漏。

很多時候為了保證資料安全,執行緒中會編寫同步程式碼,如果當執行緒正在執行同步程式碼時,此時呼叫stop,引起丟擲異常,導致執行緒持有的鎖會全部釋放,此時就不能確保資料的安全性,出現無法預期的錯亂資料,還有可能導致存在需要被釋放的資源得不到釋放,引發記憶體洩露。所以用stop停止執行緒是不推薦的。

4。優雅的終止執行緒

關閉執行緒,肯定等到執行緒釋放掉資源,以免造成記憶體洩漏,所以不建議使用stop方法。那麼應該怎麼關閉執行緒呢?答案是兩階段終止模式。

兩階段終止模式,顧名思義,分兩個階段。第一個階段,向執行緒傳送終止指令,記住這裡只是設定一個終止標識位。第二階段,執行緒響應指令,然後終止。這裡就用到了,Thread。interrupt()方法來設定終止標識位。我們先看一張圖如下:

面試官:執行緒如何啟動和優雅的終止

想終止執行緒,也就是執行緒是終止狀態,如上圖所示,執行緒只能是RUNNABLE狀態才可以到終止狀態。所以執行緒目前狀態可以能是RUNNABLE狀態,或者是休眠狀態。當執行緒是休眠狀態,當我們設定中斷標識位時,執行緒立馬會丟擲InterruptedException,並且JVM會清除中斷標識位,所以在捕獲InterruptedException異常後,需要重新設定中斷標識位。如下程式碼:

public class StopThread { public static void main(String[] args) throws InterruptedException { Thread t = new Thread(() -> { System。out。println(”worker-Thread start 。。。“); while(!Thread。currentThread()。isInterrupted()) { System。out。println(”worker-Thread working 。。。“); try { Thread。sleep(1000); } catch (InterruptedException e) { e。printStackTrace(); Thread。currentThread()。interrupt(); } } System。out。println(”worker-Thread done 。。。“); }, ”worker-Thread“); t。start(); Thread。sleep(10000); t。interrupt(); }}

執行緒t每次執行while內程式碼,休眠1000ms,然後繼續執行,主執行緒在休眠10000ms後,執行緒t設定中斷標識位,此時,如果執行緒t還在sleep中,則丟擲InterruptedException異常,JVM清除中斷標識位,重新設定中斷標識位後,下次執行while程式碼,條件不符合,執行緒執行完畢。這樣,就完成了兩階段模式。

總結:

執行緒啟動使用start方法,而執行緒run方法,不會啟動一個執行緒。

Thread。interrupt(),設定中斷標識位,具體是否結束執行緒,執行緒自己控制。

suspend()、resume()、stop(),這三個方法已經不建議使用,有可能造成死鎖和記憶體洩漏。

透過兩階段提交模式,來優雅的終止執行緒。

Top