多執行緒7

chaoshang8發表於2024-09-05
  1. 多執行緒知識點概述
    1.什麼是多執行緒
    1)一個程序中往往有多條程式執行路徑,該程式可以理解為多執行緒程式
    2)java程式的入口是main方法,從作業系統的角度看,main方法的啟動意味著啟動了一個java程序。main方法所在的程式執行路徑即為主執行緒,同時,main執行緒的啟動,會伴隨著其他執行緒的建立和啟動,如垃圾回收執行緒等
    3)執行緒的建立者:主執行緒的建立者是jvm,普通執行緒可以透過呼叫JDK的相關API進行建立

2.多執行緒的作用
1)更大的吞吐量
2)提升效能

3.多執行緒的基本使用(程式碼:test219_thread/test/demo1)
1)執行緒任務的定義
2)執行緒的建立和啟動

4.多執行緒帶來的問題(程式碼:test219_thread/test/demo2)
1)執行緒安全問題(本質上的體現是執行緒之間的共享資料出現不正確的結果)
2)活躍性問題(死鎖、飢餓、活鎖以及丟失訊號)
3)效能問題(執行緒的建立和銷燬、執行緒的上下文切換)

5.執行緒安全描述的物件(程式碼:test219_thread/test/demo3)
1)類執行緒安全
2)程式執行緒安全

6.JDK提供幾種方式來保證執行緒安全,分別是:synchronized、volatile、final、CAS、ThreadLocal

7.執行緒的狀態,官方定義的方式有六種,定義在列舉java.lang.Thread.State中
1)新建狀態: Thread.State.NEW (Thread t = new Thread();)
2)可執行狀態: Thread.State.RUNNABLE (包括兩種:在等待CPU分配時間片、執行狀態)
3)阻塞狀態: Thread.State.BLOCKED (執行緒在等待同步鎖,試圖進入同步程式碼塊、一種是IO阻塞)
4)等待狀態: Thread.State.WAITING
等待狀態意味著執行緒放棄CPU的執行資格,退出CPU的執行緒處理佇列,即向CPU發出一個訊號,不需要分配時間片給這個執行緒。同時釋放鎖。需要其他執行緒呼叫notify方法喚醒,然後轉變為可執行狀態,繼續進入CPU的執行緒處理佇列
5)計時等待狀態:Thread.State.TIMED_WAITING
計時等待狀態意味著執行緒放棄CPU的執行資格,退出CPU的執行緒處理佇列,即向CPU發出一個訊號,不需要分配時間片給這個執行緒。不會釋放鎖。直到設定的睡眠時間結束之後,然後轉變為可執行狀態,繼續進入CPU的執行緒處理佇列
6)終止狀態: Thread.State.TERMINATED

8.wait/notify機制(程式碼:test219_thread/test/demo4)
1)涉及到的方法
· wait():讓執行緒進入等待狀態,退出CPU的執行緒處理佇列,釋放鎖,執行緒被儲存到物件obj的執行緒等待集中
· notify():喚醒物件obj的執行緒等待集中的任意一個執行緒
· notifyAll():喚醒物件obj的執行緒等待集中的所有執行緒

2)注意點
· 這幾個方法必須定義在同步塊或者同步方法中,因為這些方法是用於操作執行緒狀態的方法,必須要明確到底操作的是哪個鎖上的執行緒
· 這幾個方法定義在Object中的原因:因為這幾個方法是物件監視器的方法,物件監視器其實就是同步鎖,同步鎖可以是任意物件,所以任意物件都可以呼叫的方法只能定義在Object中

9.Java記憶體模型:假如有一個執行緒的共享變數,這個變數是存在於主記憶體中的,但是為了提高效率,每個執行緒在自己的工作記憶體中有一份該變數的複製。(主記憶體對映到硬體可以理解為平常說的記憶體,而工作記憶體可以理解為CPU中的高速緩衝儲存器,CPU從高速緩衝儲存器中讀資料肯定要比從記憶體中讀資料要快得多)

10.中斷機制(程式碼:test219_thread/test/demo5)
1)在Java中,停止一個執行緒的主要機制是中斷,中斷並不是強迫終止一個執行緒,它是一種協作機制,是給執行緒傳遞一個取消訊號,但是由執行緒來決定如何以及何時退出

2)透過Thread類的interrupt()方法來向執行緒發出中斷請求

3)執行緒在不同狀態下對於中斷請求所產生的反應
· 如果一個執行緒處於新建狀態或者終止狀態,對於中斷請求幾乎是遮蔽的
· 如果一個執行緒處於RUNNABLE狀態或者阻塞狀態,對於中斷請求只是設定中斷標誌位並沒有強制終止執行緒
· 如果一個執行緒處於睡眠狀態或等待狀態,對於中斷請求是敏感的,執行緒會從睡眠狀態或等待狀態強制恢復到可執行狀態,重新進入到CPU的執行緒處理佇列,同時執行緒會丟擲中斷異常並清空中斷標誌位

4)關於設定中斷標誌位和清空中斷標誌位
· 設定中斷標誌位:指的是把中斷狀態設定為true,即Thread.currentThread().isInterrupted()返回true
· 清空中斷標誌位:指的是把中斷狀態設定為false,即Thread.currentThread().isInterrupted()返回false

5)中斷機制的應用
· 對於處於RUNNABLE狀態或者阻塞狀態的執行緒,可以透過判斷中斷狀態來決定執行緒是否要終止
· 對於處於睡眠狀態或等待狀態的執行緒,可以透過捕獲中斷異常之後來決定執行緒是否要終止

6)與執行緒中斷有關的三個方法:interrupt()、interrupted()、islnterrupted()。(均在Thread類中)

11.juc包
1)AQS
2)Lock
3)併發工具類
4)原子類

  1. 解決執行緒安全問題的方式
    一、synchronized(程式碼:test219_thread/test/demo6)
    1)synchronized的基本使用
    · 修飾語句塊
    · 修飾方法

2)synchronized的實現原理
· 修飾語句塊
· 修飾方法

3)synchronized鎖的升級

4)synchronized實現可重入的原理
· 每一個鎖關聯一個執行緒持有者和計數器,當計數器為0時表示該鎖沒有被任何執行緒持有,那麼任何執行緒都可能獲得該鎖而呼叫相應的方法;當某一執行緒請求成功後,JVM會記下鎖的持有執行緒,並且將計數器置為1;此時其它執行緒請求該鎖,則必須等待;而該持有鎖的執行緒如果再次請求這個鎖,就可以再次拿到這個鎖,同時計數器會遞增;當執行緒退出同步程式碼塊時,計數器會遞減,如果計數器為0,則釋放該鎖

5)synchronized與wait/notify機制

6)驗證同步方法中的鎖物件
· 非靜態方法的鎖物件為this
· 靜態方法的鎖物件為Class物件

二、volatile(程式碼:test219_thread/test/demo7)

三、final

四、CAS
· 主要是透過sun.misc.Unsafe來實現CAS

五、ThreadLocal
1.ThreadLocal 是什麼

1)ThreadLocal 直譯為執行緒本地,也稱為執行緒本地變數

2)意思是在 ThreadLocal 變數中填充的值只屬於當前執行緒,對其它執行緒是不可見的,從而起到執行緒隔離的作用,避免了執行緒安全問題

2.ThreadLocal 的作用(為什麼需要 ThreadLocal)

1)線上程之間隔離資料,對於同一個變數,每個執行緒都有獨立的資料
2)減少鎖的使用,如果沒有 ThreadLocal,在某些併發場景下需要加鎖來解決

3.ThreadLocal 的常用方法
· ThreadLocal() 建立 ThreadLocal 物件
· set(T value) 設定當前執行緒繫結的區域性變數
· T get() 獲取當前執行緒繫結的區域性變數
· remove() 移除當前執行緒繫結的區域性變數

4.ThreadLocal 的基本使用(程式碼:test219_thread/test/demo8)

5.ThreadLocal 常見的應用場景

1)web 請求,在 Controller 解析出使用者資訊後放入 ThreadLocal 中,在 Service 層或者其它位置可以直接透過 ThreadLocal 獲取到使用者資訊
2)JDBC,一個執行緒中需要多次運算元據庫,並且需要保證執行緒內的事務,可以在運算元據庫之前把資料庫連線放入 ThreadLocal 中,後續在本執行緒的任意方法都可以透過 ThreadLocal 獲取到資料庫連線
(共同點都是需要在一個執行緒內的不同方法獲取某一型別(使用者資訊、資料庫連線)的資料,透過使用 ThreadLoca 就可以減少引數在方法之間的傳遞)

6.ThreadLocal 的實現原理

1)ThreadLocal、ThreadLocalMap、Thread 三者關係

· ThreadLocalMap 是 ThreadLocal 的內部類,ThreadLocalMap 透過 Entry[] 來儲存資料,Entry 是 ThreadLocalMap 的內部類,key 為 ThreadLocal 物件(實際上 key 為指向 ThreadLocal 例項的弱引用),value 為 set 進 ThreadLocal 的值

· Thread 持有 ThreadLocalMap,變數名稱為 threadLocals

2)資料流轉過程

· 呼叫 set() 方法時,先獲取當前執行緒的 ThreadLocalMap 物件,如果 ThreadLocalMap 為空則建立一個,然後把 ThreadLocal 物件和 set 進 ThreadLocal 的值放入 ThreadLocalMap 的 Entry[] 中

· 呼叫 get() 方法時,先獲取當前執行緒的 ThreadLocalMap 物件,然後從 ThreadLocalMap 中根據 ThreadLocal 物件(應該是計算雜湊值再取模)找到對應的 Entry,獲取 Entry 的 value 返回

· 呼叫 remove() 方法時,先獲取當前執行緒的 ThreadLocalMap 物件,然後從 ThreadLocalMap 中根據 ThreadLocal 物件找到對應的Entry 刪除

7.ThreadLocal 記憶體洩露問題
1)ThreadLocal 記憶體洩露是指 ThreadLocalMap 中的 value 沒辦法被回收

2)記憶體洩露原因:
ThreadLocal 已經使用結束了(意味著沒有強引用指向堆中的 ThreadLocal 物件),而執行緒還存活著,JVM 在進行垃圾回收後會把只有弱引用指向的 ThreadLocal 物件回收,也就是 Entry 的 key 會被回收,但是此時 value 還在,因此就產生了記憶體洩露

3)如何避免內
記憶體洩露:在使用完 ThreadLocal 之後,呼叫 remove() 方法把 Entry 置空

8.ThreadLocal 的最佳實踐

· 避免過度使用 ThreadLocal
· 使用完 ThreadLocal 要呼叫 remove 方法進行清除,以避免記憶體洩露

9.ThreadLocal 有什麼缺點,如何改進

10.零碎問題
1)ThreadLocal 物件在業務程式碼中被建立,該物件在兩個地方被引用,被業務程式碼強引用、被 ThreadLocalMap 的 key 弱引用
2)弱引用的特點
:只要進行垃圾回收,不管 jvm 記憶體是否充足,都會回收只被弱引用的物件(如果物件同時有強弱引用時,則不回收)
3)key 設計成弱引用是為了垃圾回收時可以保證 key 指向的 ThreadLocal 物件被回收,而呼叫 remove() 方法是保證 Entry 中的 value 可以儘快被回收

4)在呼叫 get()、set() 方法的某些時候,會伴隨呼叫 expungeStaleEntry() 方法把 key 為 null 的 Entry 置空,以此來儘可能避免記憶體洩露
5)如果執行緒執行完正常銷燬,是不會存在記憶體洩露的,因為 Thread 物件被回收後,ThreadLocalMap 自然也會被回收

6)ThreadLocal 尤其是當與執行緒池結合使用時,由於執行緒需要被複用而無法銷燬,因此使用完 ThreadLocal 如果沒有及時清理,就會導致記憶體洩露
7)remove() 方法要放在 finally 中

8)ThreadLocalMap 使用開放定址法解決雜湊衝突
9)如果需要在父子執行緒之間傳遞 ThreadLocal 填充的資料,可以使用 InheritableThreadLocal
10)ThreadLocal 不能平替 synchronized,ThreadLocal 用於實現執行緒隔離,而 synchronized 用於實現執行緒同步

  1. juc包
    一、AQS

二、Lock

三、併發工具類(程式碼:test219_thread/test/demo9)
1.基本概述
1)CountDownLatch、CyclicBarrier、Semaphore 可以認為是一種控制併發流程的工具類
2)Exchanger 可以認為是執行緒間交換資料的工具類

2.CountDownLatch
1)CountDownLatch 的核心思想是透過計數器來控制執行緒的執行順序,當計數器的值降為0時,所有等待的執行緒都會被喚醒,然後開始執行下一步操作

2)CountDownLatch 的使用:test219_thread/demo9/CountDownLatchDemo

3)CountDownLatch 的執行過程
· 在主呼叫執行緒中建立 CountDownLatch,並傳入 count,count 通常為工作執行緒數量
· 在工作執行緒中呼叫 countDown() 方法,每呼叫一次 count 就減1

· 在主呼叫執行緒中呼叫 await() 方法來阻塞主呼叫執行緒

· count減到為0時,主呼叫執行緒被喚醒

4)CountDownLatch 的底層實現:基於 AQS 實現

3.CyclicBarrier
1)CyclicBarrer 的作用是讓一組執行緒達到一個屏障(同步點)時被阻塞,直到所有的執行緒到達此屏障時,才會喚醒被屏障阻塞的所有執行緒

2)CyclicBarrer 的使用:test219_thread/demo9/CyclicBarrierDemo、CyclicBarrierDemo2、CyclicBarrierDemo3、CyclicBarrierDemo4

3)CyclicBarrer 的執行過程
· 在主呼叫執行緒中建立 CyclicBarrer,並傳入 parties,parties 通常為工作執行緒數量
· 在工作執行緒中呼叫 await() 方法,每呼叫一次,就說明有一個執行緒抵達屏障,直到有 parties 個執行緒抵達屏障後,喚醒被屏障阻塞的所有執行緒

4)CyclicBarrier 的底層實現:基於 AQS 實現

4.Semaphore

1)訊號量是用來控制同時訪問特定資源的執行緒數量,它透過協調各個執行緒,以保證合理的使用公共資源

2)Semaphore 的使用:test219_thread/demo9/SemaphoreDemo

3)Semaphore 的工作流程
· 在主呼叫執行緒中建立 Semaphore,並傳入 permits,permits 為許可證數量
· 在工作執行緒中呼叫 acquire() 方法,每呼叫一次,許可證數量就減1
,當許可證數量減到為0時,再呼叫 acquire() 會被阻塞,直到已經獲得許可證的工作執行緒呼叫 release() 方法歸還許可證,然後被阻塞的執行緒會獲得許可證

4)Semaphore 的底層實現:基於 AQS 實現

5.Exchanger
Exchanger 的底層實現:使用 ThreadLocal 和 ArrayBlockingQueue 等資料結構來實現執行緒間的配對和資料交換

6.零碎問題
1)CountDownLatch 和 CyclicBarrier 的區別
· CountDownLatch 阻塞的是主執行緒,下一步動作的執行者是主執行緒,不可重複使用
· CyclicBarrier 阻塞的是其它執行緒,下一步動作的執行者是其它執行緒,可重複使用

四、原子類(程式碼:test219_thread/test/demo10)
1)原子類的出現背景:當一個執行緒更新一個變數時,程式如果沒有正確的同步,那麼這個變數對於其他執行緒來說是不可見的。我們通常使用 synchronized 或者 volatile 來保證執行緒安全的更新共享變數。在JDK1.5中,提供了 java.util.concurrent.atomic 包,這個包中的原子操作類提供了一種用法簡單,效能高效,執行緒安全地更新一個變數的方式

2)原子類的應用案例:多個執行緒進行 i++ 操作,為了執行緒安全,需要加 synchronized 鎖,鎖是比較重的,因此可以考慮使用原子類AtomicInteger 來代替 synchronized

3)原子類的底層實現是基於 volatile 和 CAS,因此可以減少鎖帶來的效能開銷

  1. 多執行緒在實際業務場景中的使用
    1.基本使用
    1)主呼叫執行緒需要等待各個執行緒的返回結果(程式碼:test/demo11/ThreadInActualBusiness.needThreadResult())
    2)主呼叫執行緒不需要等待各個執行緒的返回結果(程式碼:test/demo11/ThreadInActualBusiness.main())
    (這種場景用得相對較少,因為會出現:介面已經完成響應,但是業務處理還沒有結束的情況)

執行緒池
4.執行緒池
1)執行緒池的頂層介面和類
· Executor:Executor 是一個介面,定義了一個基本的執行方法 execute(Runnable command),用於執行給定的 Runnable 任務。它是所有執行緒池執行機制的基礎
· ExecutorService:ExecutorService 也是介面,它擴充套件了 Executor 介面,提供了更豐富的執行緒池控制方法,如 submit(),以及執行緒池的關閉方法 shutdown() ,ExecutorService 是建立和管理執行緒池的主要介面
· AbstractExecutorService:實現了 ExecutorService 介面的部分方法,為建立具體的執行緒池服務提供了基礎框架。它提供了執行緒池的一些通用實現,如關閉執行緒池的行為
· ThreadPoolExecutor:ThreadPoolExecutor 是 AbstractExecutorService 的一個具體實現,提供了完整的執行緒池實現。它允許開發者精細控制執行緒池的引數,如核心執行緒數、最大執行緒數、工作佇列型別、執行緒工廠、拒絕策略等。ThreadPoolExecutor 是 Java 中執行緒池的核心實現類

2)執行緒池的使用方式
· 直接建立 ThreadPoolExecutor
· 透過 Executors 工具類建立執行緒池(本質上也是建立 ThreadPoolExecutor)
static ExecutorService newFixedThreadPool(int nThreads)
static ExecutorService newCachedThreadPool()
static ExecutorService newSingleThreadExecutor()
static ScheduledExecutorService newScheduledThreadPool(int corePoolSize)

實現多執行緒的方式
1)繼承 Thread 類
2)實現 Runnable 介面
3)實現 Callable 介面 + FutureTask
4)執行緒池(ExectutorService 結合 Callable、Future 實現有返回結果的多執行緒)

相關文章