Java中的Semaphore(信號量)全面解析
前言
Semaphore 是 Java 并發(fā)包(java.util.concurrent)中的核心同步工具類,用于??控制對共享資源的并發(fā)訪問數(shù)量??,通過“許可證”機制實現(xiàn)資源限流。下面從多個維度全面解析其設計原理和應用實踐。
一、基本概念與核心原理??
1. 核心模型??
- ??信號量模型??:
Semaphore 維護一個??許可證計數(shù)器??(permits),通過原子操作控制資源訪問:- ??
acquire()??(P操作):申請許可證,計數(shù)器減 1;若無可用許可證,線程阻塞。 - ??
release()??(V操作):釋放許可證,計數(shù)器加 1,喚醒阻塞線程。
- ??
- ??公平性??:
- ??非公平模式(默認)??:允許線程插隊獲取許可,吞吐量高但可能引發(fā)線程饑餓。
- ??公平模式??:按 FIFO 順序分配許可,避免饑餓但性能較低。
??2. 底層實現(xiàn)??
基于 ??AQS(AbstractQueuedSynchronizer)?? 的共享鎖模式實現(xiàn):
- ??狀態(tài)變量??:AQS 的
state字段存儲當前可用許可證數(shù)量。 - ??關鍵源碼邏輯??:
// 非公平模式嘗試獲取許可 final int nonfairTryAcquireShared(int acquires) { for (;;) { int available = getState(); int remaining = available - acquires; if (remaining < 0 || compareAndSetState(available, remaining)) return remaining; // 負數(shù)表示獲取失敗 } } // 釋放許可 protected final boolean tryReleaseShared(int releases) { for (;;) { int current = getState(); int next = current + releases; if (compareAndSetState(current, next)) return true; // 喚醒阻塞線程 } }
?二、使用方式與代碼示例??
1. 基礎用法??
import java.util.concurrent.Semaphore;
public class PrinterService {
private final Semaphore semaphore = new Semaphore(3); // 初始化3個許可證
public void printDocument(String docName) throws InterruptedException {
semaphore.acquire(); // 獲取許可(阻塞直到可用)
try {
System.out.println("Printing: " + docName);
Thread.sleep(1000); // 模擬打印耗時
} finally {
semaphore.release(); // 必須釋放許可!
}
}
}??2. 高級方法:超時與批量操作??
// 嘗試獲取許可(超時控制)
if (semaphore.tryAcquire(2, 500, TimeUnit.MILLISECONDS)) {
try {
// 執(zhí)行任務
} finally {
semaphore.release(2); // 釋放多個許可
}
} else {
System.out.println("獲取許可超時!");
}3. 多線程場景示例??
public static void main(String[] args) {
PrinterService service = new PrinterService();
for (int i = 0; i < 10; i++) {
new Thread(() -> {
try {
service.printDocument("Doc-" + Thread.currentThread().getName());
} catch (InterruptedException e) {
e.printStackTrace();
}
}).start();
}
}??輸出效果??:
Printing: Doc-Thread-0
Printing: Doc-Thread-1
Printing: Doc-Thread-2 // 僅3個線程并發(fā)執(zhí)行
Thread-2 釋放許可 → Thread-3 開始打印
...
三、優(yōu)缺點分析??
| ??維度?? | ??優(yōu)點?? | ??缺點?? |
|---|---|---|
| ??性能?? | 讀操作無鎖,高并發(fā)場景吞吐量高(尤其非公平模式)。 | 寫操作需復制數(shù)組,頻繁修改時性能差(時間復雜度 O(n))。 |
| ??資源控制?? | 精準限制并發(fā)量,避免資源過載。 | 許可證泄漏(未釋放)會導致資源逐漸耗盡。 |
| ??一致性?? | 弱一致性模型,迭代器安全(基于快照)。 | 無法實時感知最新修改,迭代期間數(shù)據(jù)可能過期。 |
| ??靈活性?? | 支持動態(tài)調整許可證數(shù)量、超時控制、批量操作。 | 復雜同步需求需結合其他工具(如 CountDownLatch)。 |
四、適用場景??
1. 資源池管理??
- ??數(shù)據(jù)庫連接池??:限制同時使用的連接數(shù),防止連接耗盡。
public class ConnectionPool { private final Semaphore semaphore = new Semaphore(MAX_CONNECTIONS); private final BlockingQueue<Connection> pool = new LinkedBlockingQueue<>(); public Connection borrow() throws InterruptedException { semaphore.acquire(); return pool.take(); } public void release(Connection conn) { pool.offer(conn); semaphore.release(); } }
2. 流量控制??
- ??API 限流??:限制每秒請求數(shù),防止服務雪崩。
public class RateLimiter { private final Semaphore semaphore = new Semaphore(100); // 每秒100個請求 private final ScheduledExecutorService scheduler = Executors.newScheduledThreadPool(1); public RateLimiter() { scheduler.scheduleAtFixedRate(() -> semaphore.release(semaphore.drainPermits()), 1, 1, TimeUnit.SECONDS); } public void callAPI(Runnable task) throws InterruptedException { semaphore.acquire(); task.run(); } }
3. 物理資源共享??
- ??打印機/文件系統(tǒng)??:控制多線程訪問共享硬件資源。
semaphore.acquire(); try { Files.write(Paths.get("log.txt"), data, StandardOpenOption.APPEND); } finally { semaphore.release(); }
?五、注意事項與最佳實踐??
- ??避免許可證泄漏??:
- 確保
release()在finally塊中調用,防止異常導致許可未釋放。
- 確保
- ??合理選擇公平性??:
- 高吞吐場景用非公平模式,嚴格順序需求用公平模式。
- ??控制許可證數(shù)量??:
- 過多導致資源浪費,過少引發(fā)線程饑餓(通過監(jiān)控
availablePermits()動態(tài)調整)。
- 過多導致資源浪費,過少引發(fā)線程饑餓(通過監(jiān)控
- ??避免嵌套死鎖??:
- 若需多信號量,按固定順序獲?。ㄈ缦?A 后 B),防止交叉等待。
總結??
Semaphore 是解決??資源并發(fā)控制??的利器,尤其適合??讀多寫少、資源池限流、API控頻??等場景。其基于 AQS 的實現(xiàn)兼顧了靈活性與性能,但需注意許可證管理的原子性和釋放的可靠性。在高并發(fā)系統(tǒng)中,合理使用 Semaphore 能有效提升系統(tǒng)穩(wěn)定性與資源利用率??。
到此這篇關于Java中的Semaphore(信號量)的文章就介紹到這了,更多相關Java中Semaphore內容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關文章希望大家以后多多支持腳本之家!
相關文章
Eclipse運行android項目報錯Unable to build: the file dx.jar was not
今天小編就為大家分享一篇關于Eclipse運行android項目報錯Unable to build: the file dx.jar was not loaded from the SDK folder的解決辦法,小編覺得內容挺不錯的,現(xiàn)在分享給大家,具有很好的參考價值,需要的朋友一起跟隨小編來看看吧2018-12-12
Springboot使用jxls實現(xiàn)同sheet多個列表展示
這篇文章主要介紹了Springboot使用jxls實現(xiàn)同sheet多個列表展示,具有很好的參考價值,希望對大家有所幫助,如有錯誤或未考慮完全的地方,望不吝賜教2024-08-08
Java中使用標簽(label)來控制循環(huán)的執(zhí)行流程
java 和 label 兩個完全不相干的詞,今天我把他們連接在一起了,試想一個問題,雙循環(huán)在不使用 return 的情況下,如何停止外循環(huán)?所以本文給大家介紹了Java中使用標簽(label)來控制循環(huán)的執(zhí)行流程,需要的朋友可以參考下2024-06-06
Java之while與do-while循環(huán)的用法詳解
在上一篇文章中,給大家講解了循環(huán)的概念,并重點給大家講解了for循環(huán)的使用。但在Java中,除了for循環(huán)之外,還有while、do-while、foreach等循環(huán)形式。這篇文章給大家講解while循環(huán)的使用2023-05-05

