線程(Thread)基本方法清單:
創建線程
創建線程有三種方式:
- 繼承 Thread 類
- 實現 Runnable 接口
- 實現 Callable 接口
繼承 Thread 類
通過繼承 Thread 類創建線程的步驟:
- 定義 Thread 類的子類,並覆寫該類的 run 方法。run 方法的方法體就代表了線程要完成的任務,因此把 run 方法稱為執行體。
- 創建 Thread 子類的實例,即創建了線程對象。
- 調用線程對象的 start 方法來啟動該線程。
實現 Runnable 接口
實現 Runnable 接口優於繼承 Thread 類,因為:
- Java 不支持多重繼承,所有的類都只允許繼承一個父類,但可以實現多個接口。如果繼承了 Thread 類就無法繼承其它類,這不利於擴展。
- 類可能只要求可執行就行,繼承整個 Thread 類開銷過大。
通過實現 Runnable 接口創建線程的步驟:
- 定義 Runnable 接口的實現類,並覆寫該接口的 run 方法。該 run 方法的方法體同樣是該線程的線程執行體。
- 創建 Runnable 實現類的實例,並以此實例作為 Thread 的 target 來創建 Thread 對象,該 Thread 對象才是真正的線程對象。
- 調用線程對象的 start 方法來啟動該線程。
實現 Callable 接口
繼承 Thread 類 和 實現 Callable 接口這兩種創建線程的方式都沒有返回值。所以,線程執行完後,無法得到執行結果。但如果期望得到執行結果該怎麼做?
為了解決這個問題,Java 1.5 後,提供了 Callable 接口和 Future 接口,通過它們,可以在線程執行結束後,返回執行結果。
通過實現 Callable 接口創建線程的步驟:
- 創建 Callable 接口的實現類,並實現 call 方法。該 call 方法將作為線程執行體,並且有返回值。
- 創建 Callable 實現類的實例,使用 FutureTask 類來包裝 Callable 對象,該 FutureTask 對象封裝了該 Callable 對象的 call 方法的返回值。
- 使用 FutureTask 對象作為 Thread 對象的 target 創建並啟動新線程。
- 調用 FutureTask 對象的 get 方法來獲得線程執行結束後的返回值。
FAQ
start 和 run 方法有什麼區別
- run 方法是線程的執行體。
- start 方法會啟動線程,然後 JVM 會讓這個線程去執行 run 方法。
可以直接調用 Thread 類的 run 方法麼
- 可以。但是如果直接調用 Thread 的 run 方法,它的行為就會和普通的方法一樣。
- 為了在新的線程中執行我們的代碼,必須使用 Thread 的 start 方法。
線程休眠
使用 Thread.sleep 方法可以使得當前正在執行的線程進入休眠狀態。
使用 Thread.sleep 需要向其傳入一個整數值,這個值表示線程將要休眠的毫秒數。
Thread.sleep 方法可能會拋出 InterruptedException,因為異常不能跨線程傳播回 main 中,因此必須在本地進行處理。線程中拋出的其它異常也同樣需要在本地進行處理。
線程禮讓
Thread.yield 方法的調用聲明瞭當前線程已經完成了生命週期中最重要的部分,可以切換給其它線程來執行 。
該方法只是對線程調度器的一個建議,而且也只是建議具有相同優先級的其它線程可以運行。
終止線程
Thread 中的 stop 方法有缺陷,已廢棄。
使用 Thread.stop 停止線程會導致它解鎖所有已鎖定的監視器(由於未經檢查的 ThreadDeath 異常會在堆棧中傳播,這是自然的結果)。 如果先前由這些監視器保護的任何對象處於不一致狀態,則損壞的對象將對其他線程可見,從而可能導致任意行為。Thread.stop 的許多用法應由僅修改某些變量以指示目標線程應停止運行的代碼代替。 目標線程應定期檢查此變量,如果該變量指示要停止運行,則應按有序方式從其運行方法返回。如果目標線程等待很長時間(例如,在條件變量上),則應使用中斷方法來中斷等待。
當一個線程運行時,另一個線程可以直接通過 interrupt 方法中斷其運行狀態。
如果一個線程的 run 方法執行一個無限循環,並且沒有執行 sleep 等會拋出 InterruptedException 的操作,那麼調用線程的 interrupt 方法就無法使線程提前結束。
但是調用 interrupt 方法會設置線程的中斷標記,此時調用 interrupted 方法會返回 true。因此可以在循環體中使用 interrupted 方法來判斷線程是否處於中斷狀態,從而提前結束線程。
安全地終止線程有兩種方法:
- 定義 volatile 標誌位,在 run 方法中使用標誌位控制線程終止
- 使用 interrupt 方法和 Thread.interrupted 方法配合使用來控制線程終止
示例:使用 volatile 標誌位控制線程終止
示例:使用 interrupt 方法和 Thread.interrupted 方法配合使用來控制線程終止
守護線程
什麼是守護線程?
- 守護線程(Daemon Thread)是在後臺執行並且不會阻止 JVM 終止的線程。當所有非守護線程結束時,程序也就終止,同時會殺死所有守護線程。
- 與守護線程(Daemon Thread)相反的,叫用戶線程(User Thread),也就是非守護線程。
為什麼需要守護線程?
- 守護線程的優先級比較低,用於為系統中的其它對象和線程提供服務。典型的應用就是垃圾回收器。
如何使用守護線程?
- 可以使用 isDaemon 方法判斷線程是否為守護線程。
- 可以使用 setDaemon 方法設置線程為守護線程。 正在運行的用戶線程無法設置為守護線程,所以 setDaemon 必須在 thread.start 方法之前設置,否則會拋出 llegalThreadStateException 異常; 一個守護線程創建的子線程依然是守護線程。 不要認為所有的應用都可以分配給守護線程來進行服務,比如讀寫操作或者計算邏輯。
FAQ
sleep、yield、join 方法有什麼區別
- yield 方法 yield 方法會 讓線程從 Running 狀態轉入 Runnable 狀態。 當調用了 yield 方法後,只有與當前線程相同或更高優先級的Runnable 狀態線程才會獲得執行的機會。
- sleep 方法 sleep 方法會 讓線程從 Running 狀態轉入 Waiting 狀態。 sleep 方法需要指定等待的時間,超過等待時間後,JVM 會將線程從 Waiting 狀態轉入 Runnable 狀態。 當調用了 sleep 方法後,無論什麼優先級的線程都可以得到執行機會。 sleep 方法不會釋放“鎖標誌”,也就是說如果有 synchronized 同步塊,其他線程仍然不能訪問共享數據。
- join join 方法會 讓線程從 Running 狀態轉入 Waiting 狀態。 當調用了 join 方法後,當前線程必須等待調用 join 方法的線程結束後才能繼續執行。
為什麼 sleep 和 yield 方法是靜態的
Thread 類的 sleep 和 yield 方法將處理 Running 狀態的線程。
所以在其他處於非 Running 狀態的線程上執行這兩個方法是沒有意義的。這就是為什麼這些方法是靜態的。它們可以在當前正在執行的線程中工作,並避免程序員錯誤的認為可以在其他非運行線程調用這些方法。
Java 線程是否按照線程優先級嚴格執行
即使設置了線程的優先級,也無法保證高優先級的線程一定先執行。
原因在於線程優先級依賴於操作系統的支持,然而,不同的操作系統支持的線程優先級並不相同,不能很好的和 Java 中線程優先級一一對應。
本皮是一個有著5年工作經驗的程序員,關於Java,自己有做材料的整合,一個完整學習Java的路線,學習材料和工具。需要的夥伴可以私信我,發送“交流”後就可免費獲取。對於學習Java有任何問題(學習方法,學習效率,如何就業)都可以問我。希望你也能憑自己的努力,成為下一個優秀的程序員
閱讀更多 小哇說互聯 的文章