Java常用API_多執行緒
阿新 • • 發佈:2021-11-08
第十八章 多執行緒
18.1 基本概念
18.1.1 程式和程序的概念
- 程式 - 資料結構 + 演算法,主要指存放在硬碟上的可執行檔案。
- 程序 - 主要指執行在記憶體中的可執行檔案。
- 目前主流的作業系統都支援多程序,為了讓作業系統同時可以執行多個任務,但程序是重量級的,也就是新建一個程序會消耗CPU和記憶體空間等系統資源,因此程序的數量比較侷限。
18.1.2 執行緒的概念
- 為了解決上述問題就提出執行緒的概念,執行緒就是程序內部的程式流,也就是說作業系統內部支援多程序的,而每個程序的內部又是支援多執行緒的,執行緒是輕量的,新建執行緒會共享所在程序的系統資源,因此目前主流的開發都是採用多執行緒。
- 多執行緒是採用時間片輪轉法來保證多個執行緒的併發執行,所謂併發就是指巨集觀並行微觀序列的機制。
18.2 執行緒的建立(重中之重)
18.2.1 Thread類的概念
- java.lang.Thread類代表執行緒,任何執行緒物件都是Thread類(子類)的例項。
- Thread類是執行緒的模板,封裝了複雜的執行緒開啟等操作,封裝了作業系統的差異性。
18.2.2 建立方式
- 自定義類繼承Thread類並重寫run方法,然後建立該類的物件呼叫start方法。
- 自定義類實現Runnable介面並重寫run方法,建立該類的物件作為實參來構造Thread型別的物件,然後使用Thread型別的物件呼叫start方法。
18.2.3 相關的方法
18.2.4 執行流程
- 執行main方法的執行緒叫做主執行緒,執行run方法的執行緒叫做新執行緒/子執行緒。
- main方法是程式的入口,對於start方法之前的程式碼來說,由主執行緒執行一次,當start方法呼叫成功後執行緒的個數由1個變成了2個,新啟動的執行緒去執行run方法的程式碼,主執行緒繼續向下執行,兩個執行緒各自獨立執行互不影響。
- 當run方法執行完畢後子執行緒結束,當main方法執行完畢後主執行緒結束。
- 兩個執行緒執行沒有明確的先後執行次序,由作業系統排程演算法來決定。
18.2.5 方式的比較
- 繼承Thread類的方式程式碼簡單,但是若該類繼承Thread類後則無法繼承其它類,而實現
- Runnable介面的方式程式碼複雜,但不影響該類繼承其它類以及實現其它介面,因此以後的開發中推薦使用第二種方式。
18.2.6 匿名內部類的方式
- 使用匿名內部類的方式來建立和啟動執行緒。
18.3 執行緒的生命週期(熟悉)
- 新建狀態 - 使用new關鍵字建立之後進入的狀態,此時執行緒並沒有開始執行。
- 就緒狀態 - 呼叫start方法後進入的狀態,此時執行緒還是沒有開始執行。
- 執行狀態 - 使用執行緒排程器呼叫該執行緒後進入的狀態,此時執行緒開始執行,當執行緒的時間片執行完畢後任務沒有完成時回到就緒狀態。
- 消亡狀態 - 當執行緒的任務執行完成後進入的狀態,此時執行緒已經終止。
- 阻塞狀態 - 當執行緒執行的過程中發生了阻塞事件進入的狀態,如:sleep方法。阻塞狀態解除後進入就緒狀態。
18.4 執行緒的編號和名稱(熟悉)
- 案例題目
- 自定義類繼承Thread類並重寫run方法,在run方法中先列印當前執行緒的編號和名稱,然後將執行緒的名稱修改為"zhangfei"後再次列印編號和名稱。要求在main方法中也要列印主執行緒的編號和名稱。
18.5 常用的方法(重點)
- 案例題目
- 程式設計建立兩個執行緒,執行緒一負責列印1 ~ 100之間的所有奇數,其中執行緒二負責列印1 ~ 100之間的所有偶數。
- 在main方法啟動上述兩個執行緒同時執行,主執行緒等待兩個執行緒終止。
18.6 執行緒同步機制(重點)
18.6.1 基本概念
- 當多個執行緒同時訪問同一種共享資源時,可能會造成資料的覆蓋等不一致性問題,此時就需要對執行緒之間進行通訊和協調,該機制就叫做執行緒的同步機制。
- 多個執行緒併發讀寫同一個臨界資源時會發生執行緒併發安全問題。
- 非同步操作:多執行緒併發的操作,各自獨立執行。
- 同步操作:多執行緒序列的操作,先後執行的順序。
18.6.2 解決方案
- 由程式結果可知:當兩個執行緒同時對同一個賬戶進行取款時,導致最終的賬戶餘額不合理。
- 引發原因:執行緒一執行取款時還沒來得及將取款後的餘額寫入後臺,執行緒二就已經開始取款。
- 解決方案:讓執行緒一執行完畢取款操作後,再讓執行緒二執行即可,將執行緒的併發操作改為序列操作。
- 經驗分享:在以後的開發儘量減少序列操作的範圍,從而提高效率。
18.6.3 實現方式
- 在Java語言中使用synchronized關鍵字來實現同步/物件鎖機制從而保證執行緒執行的原子性,具體方式如下:
- 使用同步程式碼塊的方式實現部分程式碼的鎖定,格式如下:
- synchronized(類型別的引用) {
- 編寫所有需要鎖定的程式碼;
- }
- synchronized(類型別的引用) {
- 使用同步方法的方式實現所有程式碼的鎖定。
- 直接使用synchronized關鍵字來修飾整個方法即可
- 該方式等價於:
- synchronized(this) { 整個方法體的程式碼 }
18.6.4 靜態方法的鎖定
- 當我們對一個靜態方法加鎖,如:
- public synchronized static void xxx(){….}
- 那麼該方法鎖的物件是類物件。每個類都有唯一的一個類物件。獲取類物件的方式:類名.class。
- 靜態方法與非靜態方法同時使用了synchronized後它們之間是非互斥關係的。
- 原因在於:靜態方法鎖的是類物件而非靜態方法鎖的是當前方法所屬物件。
18.6.5 注意事項
- 使用synchronized保證執行緒同步應當注意:
- 多個需要同步的執行緒在訪問同步塊時,看到的應該是同一個鎖物件引用。
- 在使用同步塊時應當儘量減少同步範圍以提高併發的執行效率。
18.6.6 執行緒安全類和不安全類
- StringBuffer類是執行緒安全的類,但StringBuilder類不是執行緒安全的類。
- Vector類和 Hashtable類是執行緒安全的類,但ArrayList類和HashMap類不是執行緒安全的類。
- Collections.synchronizedList() 和 Collections.synchronizedMap()等方法實現安全。
18.6.7 死鎖的概念
- 執行緒一執行的程式碼:
- public void run(){
- synchronized(a){ //持有物件鎖a,等待物件鎖b
- synchronized(b){
- 編寫鎖定的程式碼;
- }
- synchronized(b){
- }
- synchronized(a){ //持有物件鎖a,等待物件鎖b
- }
- public void run(){
- 執行緒二執行的程式碼:
- public void run(){
- synchronized(b){ //持有物件鎖b,等待物件鎖a
- synchronized(a){
- 編寫鎖定的程式碼;
- }
- synchronized(a){
- }
- synchronized(b){ //持有物件鎖b,等待物件鎖a
- }
- public void run(){
- 注意:
- 在以後的開發中儘量減少同步的資源,減少同步程式碼塊的巢狀結構的使用!
18.6.8 使用Lock(鎖)實現執行緒同步
(1)基本概念
- 從Java5開始提供了更強大的執行緒同步機制—使用顯式定義的同步鎖物件來實現。
- java.util.concurrent.locks.Lock介面是控制多個執行緒對共享資源進行訪問的工具。
- 該介面的主要實現類是ReentrantLock類,該類擁有與synchronized相同的併發性,在以後的執行緒安全控制中,經常使用ReentrantLock類顯式加鎖和釋放鎖。
(2)常用的方法
(3)與synchronized方式的比較
- Lock是顯式鎖,需要手動實現開啟和關閉操作,而synchronized是隱式鎖,執行鎖定程式碼後自動釋放。
- Lock只有同步程式碼塊方式的鎖,而synchronized有同步程式碼塊方式和同步方法兩種鎖。
- 使用Lock鎖方式時,Java虛擬機器將花費較少的時間來排程執行緒,因此效能更好。
18.6.9 Object類常用的方法
18.6.10 執行緒池(熟悉)
(1)實現Callable介面
- 從Java5開始新增加建立執行緒的第三種方式為實現java.util.concurrent.Callable介面。
- 常用的方法如下:
(2)FutureTask類
- java.util.concurrent.FutureTask類用於描述可取消的非同步計算,該類提供了Future介面的基本實現,包括啟動和取消計算、查詢計算是否完成以及檢索計算結果的方法,也可以用於獲取方法呼叫後的返回結果。
- 常用的方法如下:
(3)執行緒池的由來
- 在伺服器程式設計模型的原理,每一個客戶端連線用一個單獨的執行緒為之服務,當與客戶端的會話結束時,執行緒也就結束了,即每來一個客戶端連線,伺服器端就要建立一個新執行緒。
- 如果訪問伺服器的客戶端很多,那麼伺服器要不斷地建立和銷燬執行緒,這將嚴重影響伺服器的效能。
(4)概念和原理
- 執行緒池的概念:首先建立一些執行緒,它們的集合稱為執行緒池,當伺服器接受到一個客戶請求後,就從執行緒池中取出一個空閒的執行緒為之服務,服務完後不關閉該執行緒,而是將該執行緒還回到執行緒池中。
- 線上程池的程式設計模式下,任務是提交給整個執行緒池,而不是直接交給某個執行緒,執行緒池在拿到任務後,它就在內部找有無空閒的執行緒,再把任務交給內部某個空閒的執行緒,任務是提交給整個執行緒池,一個執行緒同時只能執行一個任務,但可以同時向一個執行緒池提交多個任務。
(5)相關類和方法
- 從Java5開始提供了執行緒池的相關類和介面:java.util.concurrent.Executors類和java.util.concurrent.ExecutorService介面。
- 其中Executors是個工具類和執行緒池的工廠類,可以建立並返回不同型別的執行緒池,常用方法如下:
- 其中ExecutorService介面是真正的執行緒池介面,主要實現類是ThreadPoolExecutor,常用方法如下: