Java--多執行緒

curry库-04049發表於2024-08-31

目錄
  • 什麼是執行緒
      • 程序和執行緒的區別
  • 執行緒狀態圖
  • 建立執行緒(四種方式)
        • 繼承Thread類,重寫run()方法
        • 實現Runnable介面,並實現run()方法
        • 實現Callable介面
        • 使用Excutor執行緒池
  • 執行緒優先順序
  • 執行緒分類
        • 工作執行緒
        • 守護執行緒
  • 多執行緒
        • 多執行緒併發問題
          • 執行緒暫停執行條件
        • 執行緒同步+ThreadLocal
        • ThreadLocal(擴充套件)
          • 本地化執行緒概念
          • 工作原理
        • CAS(擴充套件)
          • 什麼是CAS
          • 悲觀鎖和樂觀鎖
          • CAS基本原理
          • CAS優勢
          • 存在的問題
        • 執行緒死鎖
  • 執行緒池
        • 工作原理
        • 優缺點
        • Java四種內建執行緒池
        • 執行緒間通訊(瞭解) wait-noify機制
  • 執行緒定時器
        • Timer類
        • TimerTask類

什麼是執行緒

程式執行的任務

程序就是正在執行的程式,它是系統進行資源分配和排程的基本單位,各個程序之間相互獨立,系統給每個程序分配不同的地址空間和資源

Win 作業系統工作管理員檢視應用程式執行的程序

程序和執行緒的區別

比較點 程序 執行緒
地址空間 獨立的地址空間 同一程序的執行緒共享本程序的地址空間
資源佔用 程序之間的資源相互獨立 同一程序的執行緒共享本程序的資源
健壯性 一個程序崩潰後不會對其他程序產生影響(多程序比多執行緒更加健壯) 一個執行緒崩潰後則整個程序都死掉
執行過程 獨立執行 不能獨立進行,必須依賴於程序
併發與資源消耗(都可以同時進行) 建立和切換消耗資源大 建立和切換資源小

執行緒狀態圖

![d15383eb77961d4d53011c326d8f5be](C:\Users\藍影\Documents\WeChat Files\wxid_3rvg8xgv30un22\FileStorage\Temp\d15383eb77961d4d53011c326d8f5be.png)

建立執行緒(四種方式)

繼承Thread類,重寫run()方法
public class MyThread extends Thread {   
   public void run() {  
         for ( int i = 0; i < 10; i++ )  {  
             System.out.println(“子執行緒");  
         }  
   }  
   public static void main(String[] args) {  
         MyThread myThread = new MyThread(); 
         myThread.start();  
   } 
}
實現Runnable介面,並實現run()方法
public class MyThread implements Runnable {   
   public void run() {  
         for ( int i = 0; i < 10; i++ )  {  
             System.out.println(“子執行緒");  
         }  
   }  
   public static void main(String[] args) {  
         Thread myThread = new Thread(new MyThread); 
         myThread.start();  
   } 
}
實現Callable介面

有返回值

使用Excutor執行緒池

執行緒優先順序

設定優先順序不一定優先執行

最大優先順序

yieldThread.setPriority(Thread.MAX_PRIORITY);

最小優先順序

noYieldThread.setPriority(Thread.MIN_PRIORITY);

執行緒分類

工作執行緒
守護執行緒

多執行緒

多執行緒併發問題
執行緒暫停執行條件

執行緒優先順序比較低,不能獲得 CPU 時間片

使用 sleep()方法使執行緒睡眠

透過呼叫 wait()方法,使執行緒處於等待狀態

透過呼叫 yield()方法,執行緒主動出讓 CPU 控制權

執行緒由於等待一個I/0事件處於阻塞狀態

執行緒同步+ThreadLocal

執行緒同步三種方式 加鎖 使用同步程式碼塊 使用同步方法

ThreadLocal(擴充套件)
本地化執行緒概念

當工作於執行緒中的物件使用 ThreadLocal 維護變數時,ThreadLocal 為每個使用該變數的執行緒分配一個獨立的變數副本。所以每一個執行緒都可以獨立地改變自己的副本,而不會影響其他執行緒所對應的副本。從執行緒的角度看,這個變數就像是執行緒的本地變數,這也是類名中"Local“所要表達的意思。

執行緒區域性變數並不是 Java 的新發明,很多語言在語法層面就提供執行緒區域性變數。在 Java 中沒有提供語言級支援,而以一種變通的方法,透過 ThreadLocal 的類提供支援。

所以,在Java中編寫執行緒區域性變數的程式碼相對來說要笨拙一些,這也是為什麼執行緒區域性變數沒有在 Java 開發者中得到很好的普及的原因。

工作原理

ThreadLocal 是如何做到為每一個執行緒維護一份獨立的變數副本,實現的思路很簡單:在 ThreadLocal 類中維護一個 Map 結構,用於儲存每一個執行緒的變數副本,Map 中元素的 key 為執行緒物件,而 value 對應執行緒的變數副本

CAS(擴充套件)
什麼是CAS

CAS,compare and swap 的縮寫,中文翻譯成比較並交換。

我們都知道,在 Java 語言之前,併發就已經廣泛存在並在伺服器領域得到了大量的應用。所以硬體廠商老早就在晶片中加入了大量直至併發操作的原語,從而在硬體層面提升效率。在 intel 的 CPU 中,使用 cmpxchg 指令。

在 Java 發展初期,Java 語言是不能夠利用硬體提供的這些便利來提升系統的效能的。而隨著 Java 不斷的發展,Java 本地方法 (JNI) 的出現,使得 Java 程式越過 JVM 直接呼叫本地方法提供了一種便捷的方式,因而 Java 在併發的手段上也多了起來。而在 Doug Lea 提供的 cucurenct 包中,CAS 理論是它實現整個 Java 包的基石。

悲觀鎖和樂觀鎖

synchronized 是悲觀鎖,這種執行緒一旦得到鎖,其他需要鎖的執行緒就掛起的情況就是悲觀鎖。

CAS 操作的就是樂觀鎖,每次不加鎖而是假設沒有衝突而去完成某項操作,如果因為衝突失敗就重試,直到成功為止。

CAS基本原理

CAS 操作包含三個運算元 —— 記憶體位置(V)、預期原值(A)和新值(B)。 如果記憶體位置的值與預期原值相匹配,那麼處理器會自動將該位置值更新為新值 。否則,處理器不做任何操作。無論哪種情況,它都會在 CAS 指令之前返回該位置的值。(在 CAS 的一些特殊情況下將僅返回 CAS 是否成功,而不提取當前值。)CAS 有效地說明了“我認為位置 V 應該包含值 A;如果包含該值,則將 B 放到這個位置;否則,不要更改該位置,只告訴我這個位置現在的值即可。”

通常將 CAS 用於同步的方式是從地址 V 讀取值 A,執行多步計算來獲得新值 B,然後使用 CAS 將 V 的值從 A 改為 B。如果 V 處的值尚未同時更改,則 CAS 操作成功。

類似於 CAS 的指令允許演算法執行讀-修改-寫操作,而無需害怕其他執行緒同時修改變數,因為如果其他執行緒修改變數,那麼 CAS 會檢測它(並失敗),演算法 可以對該操作重新計算。

CAS優勢

利用 CPU 的 CAS 指令,同時藉助 JNI 來完成 Java 的非阻塞演算法。其它原子操作都是利用類似的特性完成的。而整個J.U.C 都是建立在 CAS 之上的,因此對於 synchronized 阻塞演算法,J.U.C 在效能上有了很大的提升。

存在的問題
  • ABA問題。因為 CAS 需要在操作值的時候檢查下值有沒有發生變化,如果沒有發生變化則更新,但是如果一個值原來是A,變成了B,又變成了A,那麼使用 CAS 進行檢查時會發現它的值沒有發生變化,但是實際上卻變化了。ABA問題的解決思路就是使用版本號。在變數前面追加上版本號,每次變數更新的時候把版本號加一,那麼A-B-A 就會變成1A-2B-3A。

  • 從 Java1.5 開始 JDK 的 atomic 包裡提供了一個類 AtomicStampedReference 來解決ABA問題。這個類的compareAndSet 方法作用是首先檢查當前引用是否等於預期引用,並且當前標誌是否等於預期標誌,如果全部相等,則以原子方式將該引用和該標誌的值設定為給定的更新值。

    關於ABA問題參考文件: http://blog.hesey.net/2011/09/resolve-aba-by-atomicstampedreference.html

  • 迴圈時間長開銷大。自旋 CAS 如果長時間不成功,會給 CPU 帶來非常大的執行開銷。如果 JVM 能支援處理器提供的 pause 指令那麼效率會有一定的提升,pause 指令有兩個作用,第一它可以延遲流水線執行指令(de-pipeline),使CPU 不會消耗過多的執行資源,延遲的時間取決於具體實現的版本,在一些處理器上延遲時間是零。第二它可以避免在退出迴圈的時候因記憶體順序衝突(memory order violation)而引起 CPU 流水線被清空(CPU pipeline flush),從而提高CPU的執行效率。

  • 只能保證一個共享變數的原子操作。當對一個共享變數執行操作時,我們可以使用迴圈 CAS 的方式來保證原子操作,但是對多個共享變數操作時,迴圈 CAS 就無法保證操作的原子性,這個時候就可以用鎖,或者有一個取巧的辦法,就是把多個共享變數合併成一個共享變數來操作。比如有兩個共享變數i=2,j=a,合併一下ij=2a,然後用CAS來操作 ij。從 Java1.5 開始 JDK 提供了AtomicReference 類來保證引用物件之間的原子性,你可以把多個變數放在一個物件裡來進行CAS操作。

執行緒死鎖

執行緒池

工作原理
  • 程式啟動時向執行緒池中提前建立一批執行緒物件
  • 當需要執行任務時,從執行緒池中獲取一個空閒的執行緒物件
  • 任務執行完畢後,不銷燬執行緒物件,而是將其返還給執行緒池,並再次將狀態設定為空閒
優缺點
  • 減少頻繁建立和銷燬執行緒物件的時間消耗,提高了程式執行的效能(優點)
  • 執行緒池中空閒的執行緒物件,會佔用系統更多的記憶體儲存空間(缺點)

執行緒池是一種以時間換空間的效能最佳化策略

Java四種內建執行緒池

Java 語言提供了一系列執行緒池的實現,以解決實際開發中各種對執行緒池的需求

  • newCachedThreadPool
  • newFixedThreadPool
  • newScheduledThreadPool
  • newSingleThreadExecutor
執行緒間通訊(瞭解) wait-noify機制

Object類 toString() clone() equals()

Java 提供了一個精心設計的執行緒間通訊機制,使用wait()、notify() 和 notifyAll() 方法,這些方法是作為 Object 類中的 final 方法實現的。這三個方法僅在 synchronized 方法中才能被呼叫

  • volatile 實現執行緒間相互通訊

  • wait() 方法:方法告知被呼叫的執行緒退出監視器並進入等待狀態,直到其他執行緒進入相同的監視器並呼叫 notify( ) 方法

  • notify( ) 方法:通知同一物件上第一個呼叫 wait( )執行緒

  • notifyAll() 方法:通知呼叫 wait() 的所有執行緒,具有最高優先順序的執行緒將先執行

執行緒定時器

Timer類

Timer是一個普通的類,其中有幾個重要的方法

  • schedule() 方法:啟動定時器一個定時任務
  • cancel()方法:終止定時器所有定時任務

啟動一個定時任務就建立一個執行緒,執行緒會一直執行下去,直到呼叫終止定時任務

TimerTask類

TimerTask 是一個抽象類,需要實現該類

  • run()方法:定時任務邏輯
//建立一個定時任務
Timer timer = new Timer();
timer.schedule(new TimerTask() {
    @Override
    public void run() {
        // 任務執行程式碼
    }
}, 5000,1000); //延時 5s 每間隔1s 執行一次

相關文章