只做外貿(mào)的公司網(wǎng)站seo推廣公司價格
個人主頁: 進朱者赤
阿里非典型程序員一枚 ,記錄平平無奇程序員在大廠的打怪升級之路。 一起學習Java、大數(shù)據(jù)、數(shù)據(jù)結(jié)構(gòu)算法(公眾號同名)
引言
在Java中,并發(fā)編程一直是一個重要的領(lǐng)域,而JDK 8中的java.util.concurrent(JUC)包提供了豐富的同步工具類,幫助開發(fā)者更加高效地處理并發(fā)問題。本文將分層次、分邏輯地介紹這些同步工具類的底層實現(xiàn)原理、使用方法和源碼解析,并給出使用注意事項。
一、Semaphore(信號量)
1. 簡介
Semaphore是一種同步工具,它允許一定數(shù)量的線程同時訪問共享資源。通過控制信號量的許可數(shù)量,Semaphore能夠?qū)崿F(xiàn)對共享資源的并發(fā)訪問限制。
2. 適用場景
Semaphore適用于需要限制并發(fā)訪問共享資源數(shù)量的場景。例如,數(shù)據(jù)庫連接池中的連接數(shù)控制,防止過多的請求同時訪問數(shù)據(jù)庫;或者在分布式系統(tǒng)中限制某個服務(wù)能夠處理的并發(fā)請求數(shù),以保證服務(wù)的穩(wěn)定性和響應(yīng)速度。
3. 使用
Semaphore semaphore = new Semaphore(5); // 初始化信號量為5
semaphore.acquire(); // 獲取一個許可,若信號量為0則阻塞
// 訪問共享資源
semaphore.release(); // 釋放一個許可
4. 內(nèi)部原理及源碼解讀
內(nèi)部原理
Semaphore基于AQS(AbstractQueuedSynchronizer)實現(xiàn),它維護了一個許可計數(shù)器。當線程調(diào)用acquire()
方法時,如果許可計數(shù)器大于0,則直接返回;否則線程會被加入等待隊列并阻塞。當線程調(diào)用release()
方法時,許可計數(shù)器加一,并嘗試喚醒等待隊列中的一個線程。
源碼解讀
Semaphore內(nèi)部有一個類Sync,它繼承了AbstractQueuedSynchronizer。Sync有兩個子類:FairSync和NonfairSync,分別用于處理公平和非公平策略。
// Semaphore的構(gòu)造方法
public Semaphore(int permits) {sync = new NonfairSync(permits);
}public Semaphore(int permits, boolean fair) {sync = fair ? new FairSync(permits) : new NonfairSync(permits);
}
在NonfairSync
或FairSync
中,會重寫AQS的tryAcquire
和tryRelease
等方法,來實現(xiàn)對許可計數(shù)器的增減操作以及線程的同步。
5. 注意事項
- 使用Semaphore時,要確保釋放的許可數(shù)量與獲取的數(shù)量相匹配,避免造成死鎖或資源泄漏。
- 在高并發(fā)場景下,要合理設(shè)置信號量的初始值,以平衡資源利用率和并發(fā)性能。
二、CountDownLatch(倒計時鎖)
1. 簡介
CountDownLatch是一種同步工具,它允許一個或多個線程等待其他線程完成操作。通過維護一個計數(shù)器,當計數(shù)器減至0時,等待的線程將被喚醒。
2. 適用場景
CountDownLatch適用于需要等待一組線程完成某個任務(wù)后再繼續(xù)執(zhí)行的場景。例如,在啟動多個線程進行并行計算時,可以使用CountDownLatch來等待所有線程計算完成后,主線程再進行匯總處理。
3. 使用
CountDownLatch latch = new CountDownLatch(5); // 初始化計數(shù)器為5
// ...其他線程執(zhí)行操作,每完成一個操作調(diào)用latch.countDown()
latch.await(); // 當前線程等待,直到計數(shù)器減至0
4. 內(nèi)部原理及源碼解讀
內(nèi)部原理
CountDownLatch同樣基于AQS實現(xiàn),它維護了一個計數(shù)器。當線程調(diào)用countDown()
方法時,計數(shù)器減一;當計數(shù)器減至0時,AQS會喚醒等待隊列中的所有線程。
源碼解讀
CountDownLatch的核心在于AQS的state變量,它代表了計數(shù)器的值。
// CountDownLatch的構(gòu)造方法
public CountDownLatch(int count) {// 初始化計數(shù)器sync = new Sync(count);
}// Sync是CountDownLatch的內(nèi)部類,繼承了AQS
private static final class Sync extends AbstractQueuedSynchronizer {private static final long serialVersionUID = 498226498192269037L;Sync(int count) {setState(count); // 設(shè)置AQS的state為初始計數(shù)器值}// ...其他方法,如tryAcquireShared等
}
在tryAcquireShared
方法中,會檢查計數(shù)器的值是否為0,如果是則直接返回表示可以獲取共享資源,否則將當前線程加入等待隊列。當countDown
方法被調(diào)用時,會調(diào)用releaseShared
方法減少計數(shù)器的值,并嘗試喚醒等待隊列中的線程。
5. 注意事項
- 在使用CountDownLatch時,要確保所有需要等待的線程都調(diào)用了
countDown()
方法,并且計數(shù)器的初始值設(shè)置正確。 - 等待線程在調(diào)用
await()
方法后會被阻塞,直到計數(shù)器減至0,因此要避免在等待過程中執(zhí)行耗時操作或阻塞操作。
三、CyclicBarrier(循環(huán)柵欄)
1. 簡介
CyclicBarrier是一種同步工具,它允許一組線程互相等待,直到所有線程都到達某個公共屏障點(barrier point)。一旦所有線程都到達屏障點,它們可以繼續(xù)執(zhí)行后續(xù)操作。
2. 適用場景
CyclicBarrier適用于需要將一組線程分割成多個階段,并在每個階段完成后進行匯總或協(xié)調(diào)的場景。例如,在多個線程協(xié)同完成一個復雜任務(wù)時,每個線程負責不同的子任務(wù),當所有線程都完成各自子任務(wù)后,再進行下一步操作。
3. 使用
CyclicBarrier cyclicBarrier = new CyclicBarrier(5); // 初始化柵欄,需要5個線程到達
// ...多個線程執(zhí)行操作,到達屏障點時調(diào)用cyclicBarrier.await()
cyclicBarrier.await(); // 當前線程等待,直到所有線程都到達屏障點
4. 內(nèi)部原理及源碼解讀
內(nèi)部原理:
CyclicBarrier內(nèi)部使用了鎖和條件變量
來實現(xiàn)線程間的同步。當線程到達屏障點時,首先檢查是否有足夠的線程到達,如果有則繼續(xù)執(zhí)行;否則將線程加入等待隊列并阻塞。當最后一個線程到達屏障點時,喚醒所有等待的線程。
源碼解讀:
CyclicBarrier的核心在于其內(nèi)部類Generation
,它代表了屏障的某個周期。每個Generation都有一個計數(shù)器來記錄到達屏障點的線程數(shù)量。
// CyclicBarrier的構(gòu)造方法
public CyclicBarrier(int parties, Runnable barrierAction) {this.parties = parties;this.count = parties;this.barrierCommand = barrierAction;this.lock = new ReentrantLock();this.condition = lock.newCondition();this.generation = new Generation();
}// Generation內(nèi)部類
private static class Generation {boolean broken = false;int index = 0;
}
在await
方法中,線程會首先嘗試獲取鎖,然后檢查當前Generation的計數(shù)器是否為0。如果不為0,則線程會加入等待隊列并阻塞。當最后一個線程到達屏障點時,它會修改Generation的計數(shù)器并喚醒等待隊列中的所有線程。
5. 注意事項
- 在使用CyclicBarrier時,要確保所有線程都正確調(diào)用了
await()
方法,并且屏障點的線程數(shù)量設(shè)置正確。 - 如果在等待過程中發(fā)生異?;蛑袛?#xff0c;CyclicBarrier可能會處于不一致狀態(tài),因此需要妥善處理異常和中斷情況。
四、Phaser(階段執(zhí)行器)
1. 簡介
Phaser是一種更靈活的同步工具,它提供了對一組線程進行分階段同步的能力。Phaser允許線程注冊、到達、等待和觸發(fā)不同的階段,非常適合用于需要動態(tài)管理線程階段執(zhí)行的場景。
2. 適用場景
Phaser適用于那些需要將線程劃分為多個階段,并在每個階段結(jié)束時執(zhí)行特定操作的情況。例如,在多階段任務(wù)中,每個階段可能需要不同的線程數(shù)量,且階段的完成條件可能不同。使用Phaser,可以方便地對這些階段進行管理和協(xié)調(diào)。
3. 使用
使用Phaser時,首先需要創(chuàng)建一個Phaser實例,并注冊參與線程。然后,在每個階段,線程可以調(diào)用arriveAndAwaitAdvance()
方法來表示它們已經(jīng)完成了當前階段的工作,并等待其他線程完成。當所有線程都到達當前階段時,Phaser會觸發(fā)階段轉(zhuǎn)換,并允許線程進入下一個階段。
4. 內(nèi)部原理及源碼解讀
內(nèi)部原理
Phaser內(nèi)部維護了一個復雜的狀態(tài)機,包括當前階段數(shù)、已注冊的參與者數(shù)量、已到達的參與者數(shù)量等。每個線程在Phaser中都有一個到達點,當所有線程都到達當前階段時,Phaser會觸發(fā)階段轉(zhuǎn)換,并允許線程進入下一個階段。
源碼解讀
Phaser的源碼相對復雜,它涉及到了大量的狀態(tài)和計數(shù)器管理。其中,register
方法用于注冊參與者,arriveAndAwaitAdvance
方法用于表示線程到達當前階段并等待其他線程。在arriveAndAwaitAdvance
方法中,會檢查當前階段是否已經(jīng)完成,如果沒有則增加已到達的參與者數(shù)量,并可能觸發(fā)階段轉(zhuǎn)換。
深入理解Phaser的實現(xiàn)原理,查看和分析其源碼是非常有幫助的。由于Phaser的源碼較長且復雜,這里我聚焦于其核心機制,而不是完整的實現(xiàn)細節(jié)。
public class Phaser {// 表示參與者的數(shù)量,以及到達的參與者數(shù)量等狀態(tài)信息private final AtomicLong state;// 用于等待/通知的鎖private final Object lock;// 構(gòu)造函數(shù),初始化Phaserpublic Phaser() {state = new AtomicLong(Phaser.INITIAL_STATE);lock = new Object();}// 注冊一個新的參與者,或者為已注冊的參與者增加數(shù)量public void register() {// ... 省略具體的實現(xiàn)細節(jié) ...}// 參與者到達某個階段,并可能等待其他參與者public int arrive() throws InterruptedException {// ... 省略具體的實現(xiàn)細節(jié) ...return phase;}// 參與者到達并等待其他參與者,同時推進到下一個階段public int awaitAdvance(int phase) throws InterruptedException {// ... 省略具體的實現(xiàn)細節(jié) ...return nextPhase;}// ... 其他方法,如deregister, arriveAndDeregister, bulkRegister, getPhase, getRegisteredParties等 ...// 內(nèi)部狀態(tài)表示,包含參與者數(shù)量和當前階段等信息private static final long UNSET = -1L; // 用于表示未設(shè)置的值private static final long TERMINATED = Long.MAX_VALUE; // 表示Phaser已經(jīng)終止private static final int MAX_PHASE = Integer.MAX_VALUE; // 最大階段數(shù)private static final int PARTIES_MASK = 0xffff; // 參與者數(shù)量的掩碼private static final int PHASE_MASK = ~PARTIES_MASK; // 階段數(shù)的掩碼private static final long INITIAL_STATE = (UNSET & PHASE_MASK) | (0 & PARTIES_MASK); // 初始狀態(tài)// ... 其他內(nèi)部方法和變量 ...
}
上面的代碼只是一個框架,實際的Phaser實現(xiàn)要復雜得多。不過,通過這個框架,我們可以了解Phaser的一些核心組成部分:
-
狀態(tài)維護:Phaser使用一個AtomicLong類型的state變量來維護其內(nèi)部狀態(tài)。這個狀態(tài)包含了當前階段數(shù)、已注冊的參與者數(shù)量以及已到達的參與者數(shù)量等信息。通過使用位操作和掩碼,Phaser能夠在單個原子變量中高效地存儲和更新這些信息。
-
注冊與到達:register()方法用于注冊新的參與者或增加已注冊參與者的數(shù)量。arrive()方法用于表示參與者已經(jīng)完成了當前階段的工作,并可能等待其他參與者。這些方法會更新state變量中的相應(yīng)信息,并根據(jù)需要喚醒等待的線程。
-
等待與推進:awaitAdvance()方法用于等待其他參與者到達當前階段,并一起進入下一個階段。這個方法會根據(jù)state變量的狀態(tài)來決定是否需要阻塞調(diào)用線程。當所有參與者都到達當前階段時,Phaser會更新state變量以推進到下一個階段,并喚醒所有等待的線程。
-
中斷與超時:實際的Phaser實現(xiàn)還支持響應(yīng)中斷和超時。這意味著如果線程在等待過程中被中斷或超過指定的等待時間,它可以從等待狀態(tài)中退出。這些特性是通過在內(nèi)部使用鎖和其他同步機制來實現(xiàn)的。
5. 注意事項
- 在使用Phaser時,需要確保正確管理線程的注冊和注銷,避免在階段轉(zhuǎn)換時出現(xiàn)不一致的情況。
- Phaser的靈活性也帶來了一定的復雜性,因此在使用時需要深入理解其工作原理和使用方法,以避免出現(xiàn)錯誤或性能問題。
總結(jié)
橫向?qū)Ρ?/h3>
以下是以表格形式總結(jié)的JDK 8中JUC包中的Semaphore、CountDownLatch、CyclicBarrier和Phaser這四個同步工具類:
工具類 | 主要用途 | 內(nèi)部原理 | 使用場景 |
---|---|---|---|
Semaphore | 控制訪問某個或多個共享資源的線程數(shù)量 | 基于AQS實現(xiàn),維護一個許可計數(shù)器 | 需要限制并發(fā)訪問共享資源的場景,如連接池、線程池等 |
CountDownLatch | 允許一個或多個線程等待其他線程完成操作 | 基于AQS實現(xiàn),維護一個計數(shù)器 | 用于協(xié)調(diào)一組線程的執(zhí)行順序,例如啟動多個線程并行處理任務(wù),并在所有任務(wù)完成后執(zhí)行匯總操作 |
CyclicBarrier | 讓一組線程互相等待,直到所有線程都到達某個公共屏障點 | 使用鎖和條件變量實現(xiàn),維護屏障的周期和計數(shù)器 | 需要一組線程在某個點相互等待的場景,如并行計算中的初始化、數(shù)據(jù)準備等 |
Phaser | 提供對一組線程進行分階段同步的能力 | 維護復雜的狀態(tài)機,包括階段數(shù)、參與者數(shù)量和到達點 | 適用于需要將線程劃分為多個階段,并在每個階段結(jié)束時執(zhí)行特定操作的場景,如多階段任務(wù)處理 |
常見面試題
在面試中,關(guān)于JDK 8中JUC包中Semaphore、CountDownLatch、CyclicBarrier和Phaser這四個同步工具類的使用場景,可以提出以下面試題:
Semaphore使用場景面試題:
- 請描述一個你曾經(jīng)使用Semaphore解決并發(fā)問題的場景。你是如何確定需要的許可數(shù)量的?
- 在高并發(fā)環(huán)境下,如何使用Semaphore來限制對某個共享資源的訪問數(shù)量?
CountDownLatch使用場景面試題:
- 假設(shè)你正在開發(fā)一個需要等待多個線程完成初始化操作的系統(tǒng),你會如何使用CountDownLatch來實現(xiàn)?
- 請分享一個你使用CountDownLatch協(xié)調(diào)多個線程執(zhí)行順序的實例,并解釋其工作原理。
CyclicBarrier使用場景面試題:
- 描述一個適合使用CyclicBarrier的場景,并解釋為什么它比其他同步工具類更適合這個場景。
- 在一個多線程任務(wù)中,你需要在所有線程都完成某個階段后才能進行下一階段,你會如何使用CyclicBarrier來實現(xiàn)?
Phaser使用場景面試題:
- 請描述一個需要使用Phaser進行分階段同步的場景,并解釋Phaser在這個場景中的優(yōu)勢。
- 假設(shè)你正在開發(fā)一個復雜的多階段任務(wù),每個階段需要不同數(shù)量的線程來完成,你會如何使用Phaser來管理這些線程的執(zhí)行?
這些面試題旨在了解候選人對這些同步工具類應(yīng)用場景的理解以及實際應(yīng)用經(jīng)驗。通過回答這些問題,候選人可以展示他們對并發(fā)編程和JUC工具類的熟悉程度,以及解決實際問題的能力。
這些工具類都提供了靈活的同步機制,可以幫助開發(fā)者更好地控制和管理并發(fā)程序的執(zhí)行。根據(jù)具體的使用場景和需求,可以選擇合適的工具類來實現(xiàn)線程同步和協(xié)調(diào)。
以上就是JDK 8中JUC包中Semaphore、CountDownLatch、CyclicBarrier和Phaser這四個同步工具類的詳細介紹。每個類都有其獨特的使用場景和內(nèi)部原理,了解并正確使用這些工具類,可以大大提高并發(fā)編程的效率和穩(wěn)定性。
歡迎一鍵三連(關(guān)注+點贊+收藏)
,技術(shù)的路上一起加油!!!代碼改變世界
- 關(guān)于我:阿里非典型程序員一枚 ,記錄平平無奇程序員在大廠的打怪升級之路。 一起學習Java、大數(shù)據(jù)、數(shù)據(jù)結(jié)構(gòu)算法(
公眾號同名
)??歡迎關(guān)注下面的公眾號:
進朱者赤
,認識不一樣的技術(shù)人。??