多線(xiàn)程并發(fā)控制工具Semaphore的使用詳解
當(dāng)在多線(xiàn)程運(yùn)行的場(chǎng)景,部分共享資源會(huì)存在資源的沖突和競(jìng)爭(zhēng),為了改善資源使用的方式,是否可以通過(guò)控制某個(gè)方法允許并發(fā)訪(fǎng)問(wèn)線(xiàn)程的數(shù)量?
如下圖所示:

Semaphore可以有效的緩解這個(gè)問(wèn)題。
1、Semaphore類(lèi)
在jdk中提供了一個(gè)Semaphore類(lèi)(信號(hào)量)
它提供了兩個(gè)方法:
- semaphore.acquire() 請(qǐng)求信號(hào)量,可以限制線(xiàn)程的個(gè)數(shù),是一個(gè)正數(shù),如果信號(hào)量是-1,就代表已經(jīng)用完了信號(hào)量,其他線(xiàn)程需要阻塞了。
- 第二個(gè)方法是semaphore.release(),代表是釋放一個(gè)信號(hào)量,此時(shí)信號(hào)量的個(gè)數(shù)+1。
2、基本概念
2.1、信號(hào)量
Semaphore 維護(hù)了一個(gè)計(jì)數(shù)器(許可的數(shù)量),表示可以同時(shí)訪(fǎng)問(wèn)某個(gè)資源的線(xiàn)程數(shù)量。線(xiàn)程通過(guò)申請(qǐng)?jiān)S可來(lái)訪(fǎng)問(wèn)資源。
2.2、計(jì)數(shù)器
信號(hào)量的計(jì)數(shù)器可以被設(shè)置為一個(gè)初始值,該值表示許可的數(shù)量。每當(dāng)一個(gè)線(xiàn)程獲取許可時(shí),計(jì)數(shù)器減一;當(dāng)釋放許可時(shí),計(jì)數(shù)器加一。
2.3、公平性
Semaphore 可以配置為公平或非公平。公平的信號(hào)量遵循 FIFO(先入先出)原則,非公平信號(hào)量則不保證獲取的順序。
代碼示例:
import java.util.concurrent.Semaphore;
public class SemaphoreTest {
public static void main(String[] args) {
final DatabaseConnectionPool pool = new DatabaseConnectionPool(3);
// 創(chuàng)建多個(gè)線(xiàn)程以模擬數(shù)據(jù)庫(kù)連接
Thread thread1 = new Thread(() -> pool.connect("Thread 1"));
Thread thread2 = new Thread(() -> pool.connect("Thread 2"));
Thread thread3 = new Thread(() -> pool.connect("Thread 3"));
Thread thread4 = new Thread(() -> pool.connect("Thread 4"));
Thread thread5 = new Thread(() -> pool.connect("Thread 5"));
thread1.start();
thread2.start();
thread3.start();
thread4.start();
thread5.start();
}
}
class DatabaseConnectionPool{
private final Semaphore semaphore;
DatabaseConnectionPool(int maxConnections) {
this.semaphore = new Semaphore(maxConnections,true);
}
public void connect(String threadName) {
try {
System.out.println(threadName + " is trying to connect.");
// 獲取許可
semaphore.acquire();
System.out.println(threadName + " has connected to the database.");
// 模擬使用連接
Thread.sleep(5000); // 模擬數(shù)據(jù)庫(kù)操作
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
} finally {
// 釋放許可
System.out.println(threadName + " is releasing the connection.");
semaphore.release();
}
}
}代碼解析
1.Semaphore 的創(chuàng)建:
public DatabaseConnectionPool(int maxConnections) {
this.semaphore = new Semaphore(maxConnections);
}通過(guò)指定最大連接數(shù)來(lái)初始化信號(hào)量。
2.獲取連接
semaphore.acquire();
線(xiàn)程嘗試獲取信號(hào)量的許可,如果沒(méi)有可用的許可,則該線(xiàn)程會(huì)被阻塞,直到有許可可用。
3.釋放連接
semaphore.release();
訪(fǎng)問(wèn)完成后,線(xiàn)程釋放許可,讓其他線(xiàn)程能夠訪(fǎng)問(wèn)。
多線(xiàn)程模擬:
使用多個(gè)線(xiàn)程來(lái)模擬多個(gè)連接請(qǐng)求,只有 3 個(gè)線(xiàn)程能同時(shí)獲取許可。
3、使用場(chǎng)景
- 控制并發(fā)訪(fǎng)問(wèn)某些資源,例如數(shù)據(jù)庫(kù)連接、文件句柄等。
- 限制同時(shí)執(zhí)行的線(xiàn)程數(shù)量,以避免系統(tǒng)負(fù)載過(guò)大。
4、死鎖
死鎖是一種情況,其中兩個(gè)或多個(gè)線(xiàn)程永遠(yuǎn)互相等待對(duì)方釋放資源,從而導(dǎo)致程序無(wú)法繼續(xù)執(zhí)行。
了解更多死鎖知識(shí),可參考:有關(guān)Java死鎖和活鎖的聯(lián)系
4.1、條件
- 互斥條件:至少有一個(gè)資源處于非共享模式,即某一時(shí)刻只能被一個(gè)線(xiàn)程使用。
- 保持并等待條件:一個(gè)線(xiàn)程保持至少一個(gè)資源并等待其他被其他線(xiàn)程占用的資源。
- 不剝奪條件:資源不能被強(qiáng)行奪走,只能由持有該資源的線(xiàn)程釋放。
- 循環(huán)等待條件:存在一個(gè)線(xiàn)程的集合,使得每個(gè)線(xiàn)程都在等待下一個(gè)線(xiàn)程持有的資源。
4.2、解決策略
- 資源順序申請(qǐng):確保所有線(xiàn)程按照相同的順序請(qǐng)求多個(gè)資源,這樣可以避免循環(huán)等待。
- 設(shè)置超時(shí):在請(qǐng)求資源時(shí)設(shè)置超時(shí),如果請(qǐng)求在一定時(shí)間內(nèi)沒(méi)有成功,線(xiàn)程應(yīng)該釋放它已持有的資源,有可能中斷互斥條件。
- 使用
tryLock和tryAcquire:可使用Lock和Semaphore的嘗試獲取方法,在未能成功獲取時(shí),可以做適當(dāng)?shù)腻e(cuò)誤處理或重試,而不是靜默等待。 - 避免持有狀態(tài):盡量避免在一個(gè)線(xiàn)程中持有多個(gè)鎖,或者隔離資源,以減少死鎖風(fēng)險(xiǎn)。
4.3、聯(lián)系
雖然 Semaphore 可以在某種情況下幫助減少發(fā)生死鎖的機(jī)會(huì),但它并不是解決死鎖問(wèn)題的直接手段。
Semaphore 控制訪(fǎng)問(wèn)的方式可以導(dǎo)致某些設(shè)計(jì)上的改善,例如:
- 限制資源的同時(shí)訪(fǎng)問(wèn):
Semaphore可用于限制可同時(shí)訪(fǎng)問(wèn)某種資源的線(xiàn)程數(shù)量,從而減少?gòu)?fù)雜的資源使用模式。 - 避免持有過(guò)多的鎖:通過(guò)合理設(shè)計(jì)線(xiàn)程的資源申請(qǐng)和釋放邏輯,結(jié)合
Semaphore,可以減少因線(xiàn)程在持有多個(gè)資源時(shí)發(fā)生互斥和等待的可能性。
總結(jié)
Semaphore 是一個(gè)非常有用的并發(fā)控制工具,可以有效地控制對(duì)共享資源的訪(fǎng)問(wèn)。通過(guò)合理使用它,可以避免過(guò)多線(xiàn)程同時(shí)訪(fǎng)問(wèn)相同資源造成的競(jìng)爭(zhēng)和沖突,從而提高并發(fā)程序的安全性和效率。
以上為個(gè)人經(jīng)驗(yàn),希望能給大家一個(gè)參考,也希望大家多多支持腳本之家。
相關(guān)文章
SpringCloud Feign遠(yuǎn)程調(diào)用實(shí)現(xiàn)詳解
Feign是Netflix公司開(kāi)發(fā)的一個(gè)聲明式的REST調(diào)用客戶(hù)端; Ribbon負(fù)載均衡、 Hystrⅸ服務(wù)熔斷是我們Spring Cloud中進(jìn)行微服務(wù)開(kāi)發(fā)非?;A(chǔ)的組件,在使用的過(guò)程中我們也發(fā)現(xiàn)它們一般都是同時(shí)出現(xiàn)的,而且配置也都非常相似2022-11-11
SpringBoot中優(yōu)化if-else語(yǔ)句的七種方法
if-else語(yǔ)句是控制流程的基本工具,但過(guò)度使用會(huì)使代碼變得復(fù)雜且難以維護(hù),在SpringBoot , SpringCloud項(xiàng)目中,優(yōu)化if-else結(jié)構(gòu)變得尤為重要,本文將深入探討七種策略,旨在減少SpringBoot , SpringCloud項(xiàng)目中 if-else的使用,需要的朋友可以參考下2024-07-07
Spring?Boot?使用觀察者模式實(shí)現(xiàn)實(shí)時(shí)庫(kù)存管理的步驟
在現(xiàn)代軟件開(kāi)發(fā)中,實(shí)時(shí)數(shù)據(jù)處理非常關(guān)鍵,本文提供了一個(gè)使用SpringBoot和觀察者模式開(kāi)發(fā)實(shí)時(shí)庫(kù)存管理系統(tǒng)的詳細(xì)教程,步驟包括創(chuàng)建項(xiàng)目、定義實(shí)體類(lèi)、實(shí)現(xiàn)觀察者模式、集成Spring框架、創(chuàng)建RESTful?API端點(diǎn)和測(cè)試應(yīng)用等,這將有助于開(kāi)發(fā)者構(gòu)建能夠即時(shí)響應(yīng)庫(kù)存變化的系統(tǒng)2024-09-09
alibaba?seata服務(wù)端具體實(shí)現(xiàn)
seata是來(lái)處理分布式服務(wù)之間互相調(diào)用的事務(wù)問(wèn)題,本文重點(diǎn)給大家介紹alibaba-seata實(shí)現(xiàn)方法,文中通過(guò)實(shí)例代碼給大家介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或工作具有一定的參考借鑒價(jià)值,需要的朋友可以參考下2022-02-02
Java線(xiàn)程池用法實(shí)戰(zhàn)案例分析
這篇文章主要介紹了Java線(xiàn)程池用法,結(jié)合具體案例形式分析了java線(xiàn)程池創(chuàng)建、使用、終止等相關(guān)操作技巧與使用注意事項(xiàng),需要的朋友可以參考下2019-10-10
如何在Java中調(diào)用python文件執(zhí)行詳解
豐富的第三方庫(kù)使得python非常適合用于進(jìn)行數(shù)據(jù)分析,最近在項(xiàng)目中就涉及到j(luò)ava調(diào)用python實(shí)現(xiàn)的算法,下面這篇文章主要給大家介紹了關(guān)于如何在Java中調(diào)用python文件執(zhí)行的相關(guān)資料,需要的朋友可以參考下2022-05-05
Spring內(nèi)置定時(shí)任務(wù)調(diào)度@Scheduled使用詳解
這篇文章主要介紹了Spring內(nèi)置定時(shí)任務(wù)調(diào)度@Scheduled使用詳解,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友可以參考下2020-12-12

