1. 程式人生 > 其它 >Java常用API_多執行緒

Java常用API_多執行緒

第十八章 多執行緒

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(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){
          • 編寫鎖定的程式碼;
        • }
      • }
    • }
  • 執行緒二執行的程式碼:
    • public void run(){
      • synchronized(b){ //持有物件鎖b,等待物件鎖a
        • synchronized(a){
          • 編寫鎖定的程式碼;
        • }
      • }
    • }
  • 注意:
    • 在以後的開發中儘量減少同步的資源,減少同步程式碼塊的巢狀結構的使用!

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,常用方法如下: