線程暫停和恢復 vs2013 線程暫停和恢復C語言



文章插圖
線程暫停和恢復 vs2013 線程暫停和恢復C語言

文章插圖
本節講解的內容是線程的初始化、線程啟動、線程中斷、suspend()、resume()、stop()和優雅的關閉線程 。1.線程初始化和線程的啟動
首先看下Thread的構造方法:
public Thread(Runnable target, String name) {init(null, target, name, 0);}當然Thread有多個構造方法,都是調用init方法初始化一個線程 ??聪耰nit的定義:
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 interruptedat 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方法為什么不建議使用呢?我們先看源碼:
@Deprecatedpublic 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 statesstop0(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代碼,條件不符合,線程執行完畢 。這樣,就完成了兩階段模式 。
總結:
【線程暫停和恢復 vs2013 線程暫停和恢復C語言】線程啟動使用start方法,而線程run方法,不會啟動一個線程 。Thread.interrupt(),設置中斷標識位,具體是否結束線程,線程自己控制 。suspend()、resume()、stop(),這三個方法已經不建議使用,有可能造成死鎖和內存泄漏 。通過兩階段提交模式,來優雅的終止線程 。