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