Java中的Semaphore(信號量)全面解析
前言
Semaphore 是 Java 并發(fā)包(java.util.concurrent)中的核心同步工具類,用于??控制對共享資源的并發(fā)訪問數(shù)量??,通過“許可證”機制實現(xiàn)資源限流。下面從多個維度全面解析其設(shè)計原理和應(yīng)用實踐。
一、基本概念與核心原理??
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字段存儲當(dāng)前可用許可證數(shù)量。 - ??關(guān)鍵源碼邏輯??:
// 非公平模式嘗試獲取許可 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. 基礎(chǔ)用法??
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í)行任務(wù)
} 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ā)場景吞吐量高(尤其非公平模式)。 | 寫操作需復(fù)制數(shù)組,頻繁修改時性能差(時間復(fù)雜度 O(n))。 |
| ??資源控制?? | 精準限制并發(fā)量,避免資源過載。 | 許可證泄漏(未釋放)會導(dǎo)致資源逐漸耗盡。 |
| ??一致性?? | 弱一致性模型,迭代器安全(基于快照)。 | 無法實時感知最新修改,迭代期間數(shù)據(jù)可能過期。 |
| ??靈活性?? | 支持動態(tài)調(diào)整許可證數(shù)量、超時控制、批量操作。 | 復(fù)雜同步需求需結(jié)合其他工具(如 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ù),防止服務(wù)雪崩。
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塊中調(diào)用,防止異常導(dǎo)致許可未釋放。
- 確保
- ??合理選擇公平性??:
- 高吞吐場景用非公平模式,嚴格順序需求用公平模式。
- ??控制許可證數(shù)量??:
- 過多導(dǎo)致資源浪費,過少引發(fā)線程饑餓(通過監(jiān)控
availablePermits()動態(tài)調(diào)整)。
- 過多導(dǎo)致資源浪費,過少引發(fā)線程饑餓(通過監(jiān)控
- ??避免嵌套死鎖??:
- 若需多信號量,按固定順序獲?。ㄈ缦?A 后 B),防止交叉等待。
總結(jié)??
Semaphore 是解決??資源并發(fā)控制??的利器,尤其適合??讀多寫少、資源池限流、API控頻??等場景。其基于 AQS 的實現(xiàn)兼顧了靈活性與性能,但需注意許可證管理的原子性和釋放的可靠性。在高并發(fā)系統(tǒng)中,合理使用 Semaphore 能有效提升系統(tǒng)穩(wěn)定性與資源利用率??。
到此這篇關(guān)于Java中的Semaphore(信號量)的文章就介紹到這了,更多相關(guān)Java中Semaphore內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
Java避免UTF-8的csv文件打開中文出現(xiàn)亂碼的方法
這篇文章主要介紹了Java避免UTF-8的csv文件打開中文出現(xiàn)亂碼的方法,結(jié)合實例形式分析了java操作csv文件時使用utf-16le編碼與utf8編碼相關(guān)操作技巧,需要的朋友可以參考下2019-07-07
java并發(fā)學(xué)習(xí)之BlockingQueue實現(xiàn)生產(chǎn)者消費者詳解
這篇文章主要介紹了java并發(fā)學(xué)習(xí)之BlockingQueue實現(xiàn)生產(chǎn)者消費者詳解,具有一定參考價值,需要的朋友可以了解下。2017-11-11
Java使用Arrays.asList報UnsupportedOperationException的解決
這篇文章主要介紹了Java使用Arrays.asList報UnsupportedOperationException的解決,文中通過示例代碼介紹的非常詳細,對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧2021-04-04

