1. 程式人生 > >Java—執行緒的生命週期及執行緒控制方法詳解

Java—執行緒的生命週期及執行緒控制方法詳解

# 執行緒生命週期5種狀態 ## 介紹   執行緒的生命週期經過**新建(New)、就緒(Runnable)、執行(Running)、阻塞(Bolocked)和死亡(Dead)** ## 狀態轉換圖 ![在這裡插入圖片描述](https://img-blog.csdnimg.cn/20200519185512822.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L0FuZHlhX25ldA==,size_16,color_FFFFFF,t_70) ## 新建(New)   程式使用`new關鍵字`建立一個執行緒之後,該執行緒就`處於新建狀態`,僅僅由Java虛擬機器為其**分配記憶體**,並**初始化其成員變數的值**。**不會執行執行緒的執行緒執行體**。如`Thread thread = new Thread()`。 ## 就緒(Runnable)   也稱為`“可執行狀態”`,執行緒物件呼叫`start()`方法後,該執行緒`處於就緒狀態`。如`thread.start()`。Java虛擬機器會為其**建立方法呼叫棧和程式計數器**(執行緒私有),處於就緒狀態的執行緒並沒有開始執行,只是**表示該執行緒可以執行**,執行緒何時執行取決於JVM中執行緒排程器的排程。 ## 執行(Running)   處於就緒狀態的執行緒**獲得CPU**,開始執行`run()方法`的執行緒執行體,則該執行緒`處於執行狀態`。(注意:執行緒只能從就緒狀態進入到執行狀態) ## 阻塞(Boloked)   阻塞狀態是執行緒因為某種原因`放棄了CPU的使用權`,暫時停止執行,直到執行緒進入就緒狀態,才有機會轉到執行狀態。當呼叫`sleep()`、一個阻塞式`IO方法`、同步鎖、等待通知、`suspend()方法`掛起都會使執行緒進入阻塞狀態。 - 執行緒呼叫`sleep()`方法主動放棄所佔用的處理器資源; - 執行緒呼叫一個阻塞式`IO方法`,在該方法返回之前,該執行緒被阻塞; - 執行緒試圖獲得一個`同步監視器`,但該同步監視器正被其他執行緒所持有; - 執行緒在等待(`wait()`)某個通知(`notify()`); - 程式呼叫了執行緒的`suspend()`方法將該執行緒掛起,但這個方法易造成死鎖,應該避免使用。   執行緒從**阻塞狀態解除**——**進入就緒狀態**的過程: - 呼叫`sleep()方法`的執行緒**經過了指定時間**; - 執行緒呼叫的阻塞式**IO方法已經返回**; - 執行緒成功地**獲得**試圖取得的**同步監視器(鎖)**; - 執行緒正在等待某個通知時,**其他執行緒發出了一個通知**; - 處於掛起狀態的執行緒被呼叫了`resume()`恢復方法。 ## 死亡(Dead) ### 以如下3種方式結束執行緒 - `run()`或`call()`方法執行完成,執行緒正常結束; - 執行緒丟擲一個未捕獲的`Exception`或`Error`; - 直接呼叫該執行緒的`stop()`方法來結束該執行緒(該方法易造成死鎖,不推薦使用) **注意:** - 當丟擲一個異常後程序會結束,所以執行緒會終止; - sleep()方法會阻塞一個執行緒並不會終止; - 建立一個新的執行緒也不會終止另一個執行緒。 ### 判斷執行緒是否死亡   可以通過`isAlive()`方法,執行緒物件的`isAlive()`方法返回true,即為執行緒存活;返回false,即為執行緒死亡。   執行緒處於**就緒、執行、阻塞狀態**時,`isAlive()`返回`true`;執行緒處於**新建、死亡狀態**時,`isAlive()`返回`false`。 # start()和run()方法詳解 ## start()和run()介紹   當程式使用`new關鍵字`建立了一個執行緒後,該執行緒就處於新建狀態,此時它和其他Java物件是一樣的,只是由JVM為其分配記憶體,並初始化其成員變數的值(此時執行緒物件沒有任何的行為,也不執行執行緒執行體)。   當執行緒物件呼叫了`start()`方法後,執行緒就處於就緒狀態,JVM為其建立方法呼叫棧和程式計數器,處於這個狀態中的執行緒還沒有真正的開始執行,只是表示這個執行緒此時是一個可執行狀態。何時能執行?取決於JVM的執行緒排程器的排程。   處於就緒狀態的執行緒獲取`CPU執行許可權`,開始執行`run()`方法的執行緒執行體,此時執行緒處於執行狀態。(若只有一個CPU,任何時刻只能有一個執行緒處於執行狀態,多執行緒處理任務時,會給人一種併發錯覺,實際是CPU執行速度較快,多執行緒交織執行任務而已) ## start()方法原始碼 ```java public synchronized void start() { /** * This method is not invoked for the main method thread or "system" * group threads created/set up by the VM. Any new functionality added * to this method in the future may have to also be added to the VM. * * A zero status value corresponds to state "NEW". */ //若執行緒不是就緒狀態,就丟擲異常 if (threadStatus != 0) throw new IllegalThreadStateException(); /* Notify the group that this thread is about to be started * so that it can be added to the group's list of threads * and the group's unstarted count can be decremented. */ //將執行緒新增到ThreadGroup中 group.add(this); boolean started = false; try { //通過start0()方法啟動執行緒 start0(); //設定執行緒啟動的started標誌位 started = true; } finally { try { if (!started) { group.threadStartFailed(this); } } catch (Throwable ignore) { /* do nothing. If start0 threw a Throwable then it will be passed up the call stack */ } } } ```   `start()`實際上通過本地方法`start0()`啟動執行緒,會新執行一個執行緒,新執行緒會呼叫`run()`方法。 ## run()方法原始碼 ```java @Override public void run() { if (target != null) { target.run(); } } ```   `target`是`Runnable物件`,`run()`直接呼叫`Thread執行緒`的`Runnable成員`的`run()`方法,並不會新建一個執行緒。 # 執行緒控制方法 ## sleep()方法 ### sleep()方法介紹 1. sleep(long millis)方法是Thread類的一個靜態方法,作用是讓當前執行緒暫停一段時間,並進入阻塞狀態。 ### sleep()方法過載方式 - `public static native void sleep(long millis) throws InterruptedException`:讓當前正在執行的執行緒暫停millis毫秒,並進入阻塞狀態。 - `public static void sleep(long millis, int nanos) throws InterruptedException`:讓當前正在執行的執行緒暫停millis毫秒+nanos毫微秒,並進入阻塞狀態。(很少用) ### sleep()示例 通常用法就是 ```java //讓當前執行緒睡眠1000毫秒,即暫定1s Thread.sleep(1000); ``` ## yield()方法 ### yield()方法介紹 1. `yield()`方法讓當前正在執行的執行緒暫停,但**不會阻塞執行緒**,只是讓執行緒轉入就緒狀態。 2. `yield()`方法讓當前執行緒暫停,讓系統的**執行緒排程重新排程一次**,所以會出現當某個執行緒呼叫了yield()方法後,執行緒排程器又重新將它排程出來執行。 3. `yield()`方法讓當前執行緒暫停後,只有**優先順序>=當前執行緒**的處於**就緒狀態**的執行緒才能獲取CPU執行許可權。 ### yield()方法過載 - `public static native void yield();`:靜態方法。 ### yield()示例 ```java //讓當前執行緒暫停 Thread.yield(); ``` ### 執行緒優先順序 1. 每個執行緒執行都有一定的優先順序,優先順序高的執行緒獲得CPU執行許可權的機會比較大。 2. 每個執行緒預設的優先順序與建立它的父執行緒的優先順序相同。所以main執行緒的優先順序一般和自己建立的子執行緒優先順序一樣。 3. Thread類提供`setPriority(int newPriority)`和`getPriority()`方法設定和返回指定執行緒的優先順序。其中setPriority()方法的引數可以是一個整數(1-10之間),也可以是靜態常量。 MAX_PRIORITY:值為10. MIN_PRIORITY:值為1. NORM_PRIORITY:值為5. ## join()方法 ### join()方法介紹 1. Thread類提供`join()方法`讓一個執行緒等待另一個執行緒完成的方法;就是將指定的執行緒加入到當前執行緒,這樣兩個交替執行的執行緒就變成了順序執行的執行緒,如執行緒A呼叫了執行緒B的join()方法,則執行緒A會等待執行緒B執行完畢後才會繼續執行自己。 2. `join()`方法由使用執行緒的程式呼叫,呼叫執行緒呼叫執行緒t的t.join()方法後將會被阻塞,直到執行緒t執行完畢,呼叫執行緒才能繼續執行。一般就是用於主執行緒內,等待其他執行緒執行完畢後,再繼續執行main()函式。 ### join()方法過載方式 - `public final void join() throws InterruptedException`:等待被join的執行緒執行完畢。 - `public final synchronized void join(long millis) throws InterruptedException`:等待被join的執行緒的超時時間為millis毫秒。如果在millis毫秒內被join的執行緒還未結束執行流程,則呼叫執行緒不再等待。 - `public final synchronized void join(long millis, int nanos) throws InterruptedException`:等待被join的執行緒的時間最長為millis毫秒+nanos毫微秒。(很少用) ### join()方法示例 ###### (1)未使用join()方法 **程式碼** ```java public class JoinMethodTest { public static void main(String[] args) { System.out.println("main thread start"); Thread thread = new Thread(new Runnable() { @Override public void run() { System.out.println("child thread start"); try { Thread.sleep(1000); } catch (Exception e) { e.printStackTrace(); } System.out.println("child thread finshed"); } }); thread.start(); System.out.println("main thread finshed"); } } ``` **執行結果** ```java main thread start main thread finshed child thread start child thread finshed ```   可以從執行結果看出,main()主執行緒日誌列印的很快,沒有等待子執行緒列印就結束了。 ###### (2)使用join()方法 **程式碼** ```java public class JoinMethodTest { public static void main(String[] args) { System.out.println("main thread start"); Thread thread = new Thread(new Runnable() { @Override public void run() { System.out.println("child thread start"); try { Thread.sleep(1000); } catch (Exception e) { e.printStackTrace(); } System.out.println("child thread finshed"); } }); thread.start(); //加入join()方法等待子執行緒執行完畢,才執行主執行緒。 try { thread.join(); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println("main thread finshed"); } } ``` **執行結果** ```java main thread start child thread start child thread finshed main thread finshed ```   從執行結果可以看出,`main thread finshed`結果是在最後列印的,加入join()方法等待子執行緒執行完畢,才執行主執行緒。 # 6種狀態的執行緒生命週期解釋 ![在這裡插入圖片描述](https://img-blog.csdnimg.cn/20200519103408546.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L0FuZHlhX25ldA==,size_16,color_FFFFFF,t_70) # Q&A ## 為何啟動執行緒需要用start()方法而不是直接呼叫run()方法? 1. 呼叫`start()`方法啟動執行緒,系統會將該執行緒物件的`run()`方法當作執行緒執行體來處理。 2. 直接呼叫執行緒物件的`run()`方法,該方法會被立即執行,而在`run()`方法返回之前其他執行緒無法併發執行(系統會將執行緒物件的當作一個普通物件,將`run()`方法當作一個普通方法,而不是執行緒執行體。) ## start()方法和run()方法 ###### java Thread中,run方法和start()方法的區別 - **概念**:`start()`是啟動執行緒,讓執行緒從新建狀態變為就緒狀態;執行緒得到CPU時間片後,執行`run()`中的執行緒執行體; - **呼叫次數**:`start()`只能呼叫一次;`run()`可以重複呼叫。 - **方法型別**:啟動執行緒只能用`start()`,系統會把`run()`方法當做執行緒執行體處理;如果直接呼叫`run()`,系統會把執行緒物件當作普通物件,此時`run()`也是一個普通方法,而不是執行緒執行體。run()方法只是類的一個普通方法而已,如果直接呼叫run方法,程式中依然只有主執行緒這一個執行緒,其程式執行路徑還是隻有一條,還是要順序執行,還是要等待run方法體執行完畢後才可繼續執行下面的程式碼。。 - **原始碼**:`start()`原始碼中實際上通過本地方法`start0()`啟動執行緒,會新執行一個執行緒,新執行緒會呼叫`run()`方法;而`run()`原始碼中`target`是`Runnable物件`,`run()`直接呼叫`Thread執行緒`的`Runnable`成員的`run()`方法,並不會新建一個執行緒。 - **多執行緒**:用 start方法來啟動執行緒,是真正實現了多執行緒, 通過呼叫Thread類的start()方法來啟動一個執行緒,這時此執行緒處於就緒(可執行)狀態,並沒有執行,一旦得到cpu時間片,就開始執行run()方法。但要注意的是,此時無需等待run()方法執行完畢,即可繼續執行下面的程式碼。所以run()方法並沒有實現多執行緒。 ## sleep()和yield()方法的區別 1. **依賴執行緒優先順序**:sleep()方法暫停當前執行緒後,會給其他執行緒執行機會,而不在乎其他執行緒的優先順序; yield()方法暫停當前執行緒後,只會給優先順序相同或更高的執行緒執行機會。 2. **執行緒轉入狀態**:sleep()方法將執行緒轉入阻塞狀態,知道經過阻塞時間才會轉入就緒狀態; yield()方法不會將執行緒轉入阻塞狀態,而是將執行緒轉入就緒狀態。 3. **異常宣告**:sleep()方法宣告丟擲了InterruptedException異常; yield()方法未宣告丟擲異常。 4. **可移植性**: sleep()方法的移植性比yield()方法好,所以一般使用sleep()方法控制併發