Semaphore(信號量)是用來控制同時訪問特定資源的線程數量,它通過協調各個線程,以保證合理的使用公共資源。以前我都覺得從字面上很難理解Semaphore所表達的含義,只能把它比作是控制流量的紅綠燈,比如XX馬路要限制流量,只允許同時有一百輛車在這條路上行使,其他的都必須在路口等待,所以前一百輛車會看到綠燈,可以開進這條馬路,後面的車會看到紅燈,不能駛入XX馬路,但是如果前一百輛中有五輛車已經離開了XX馬路,那麼後面就允許有5輛車駛入馬路,這個例子裏說的車就是線程,駛入馬路就表示線程在執行,離開馬路就表示線程執行完成,看見紅燈就表示線程被阻塞,不能執行。
應用場景
Semaphore可以用於做流量控制,特別公用資源有限的應用場景,比如數據庫連接。假如有一個需求,要讀取幾萬個文件的數據,因爲都是IO密集型任務,我們可以啓動幾十個線程併發的讀取,但是如果讀到內存後,還需要存儲到數據庫中,而數據庫的連接數只有10個,這時我們必須控制只有十個線程同時獲取數據庫連接保存數據,否則會報錯無法獲取數據庫連接。這個時候,我們就可以使用Semaphore來做流控,代碼如下:
public class SemaphoreTest {
private static final int THREAD_COUNT = 30;
private static ExecutorService threadPool = Executors
.newFixedThreadPool(THREAD_COUNT);
private static Semaphore s = new Semaphore(10);
public static void main(String[] args) {
for (int i = 0; i < THREAD_COUNT; i++) {
threadPool.execute(new Runnable() {
@Override
public void run() {
try {
s.acquire();
System.out.println("save data");
s.release();
} catch (InterruptedException e) {
}
}
});
}
threadPool.shutdown();
}
}
在代碼中,雖然有30個線程在執行,但是隻允許10個併發的執行。Semaphore的構造方法Semaphore(int permits) 接受一個整型的數字,表示可用的許可證數量。Semaphore(10)表示允許10個線程獲取許可證,也就是最大併發數是10。Semaphore的用法也很簡單,首先線程使用Semaphore的acquire()獲取一個許可證,使用完之後調用release()歸還許可證。還可以用tryAcquire()方法嘗試獲取許可證。
其他方法
Semaphore還提供一些其他方法:
- int availablePermits() :返回此信號量中當前可用的許可證數。
- int getQueueLength():返回正在等待獲取許可證的線程數。
- boolean hasQueuedThreads() :是否有線程正在等待獲取許可證。
- void reducePermits(int reduction) :減少reduction個許可證。是個protected方法。
- Collection getQueuedThreads() :返回所有等待獲取許可證的線程集合。是個protected方法。
面試題思考
在很多情況下,可能有多個線程需要訪問數目很少的資源。假想在服務器上運行着若干個回答客戶端請求的線程。這些線程需要連接到同一數據庫,但任一時刻只能獲得一定數目的數據庫連接。你要怎樣才能夠有效地將這些固定數目的數據庫連接分配給大量的線程?
答:
1.給方法加同步鎖,保證同一時刻只能有一個人去調用此方法,其他所有線程排隊等待,但是此種情況下即使你的數據庫鏈接有10個,也始終只有一個處於使用狀態。這樣將會大大的浪費系統資源,而且系統的運行效率非常的低下。
2.另外一種方法當然是使用信號量,通過信號量許可與數據庫可用連接數相同的數目,將大大的提高效率和性能。
使用Semaphore實現線程同步示例
下面是一個使用Semaphore實現線程同步的示例,假設有5個線程需要同時訪問某個共享資源,但最多隻能允許2個線程訪問:
import java.util.concurrent.Semaphore;
public class SemaphoreExample {
private static final int THREAD_COUNT = 5;
private static final int MAX_PERMITS = 2;
private static Semaphore semaphore = new Semaphore(MAX_PERMITS);
public static void main(String[] args) {
for (int i = 0; i < THREAD_COUNT; i++) {
Thread thread = new Thread(new Worker(i));
thread.start();
}
}
static class Worker implements Runnable {
private int workerId;
public Worker(int workerId) {
this.workerId = workerId;
}
@Override
public void run() {
try {
System.out.println("Worker " + workerId + " is waiting...");
semaphore.acquire();
System.out.println("Worker " + workerId + " is accessing the shared resource.");
Thread.sleep(2000); // 模擬訪問共享資源的耗時操作
System.out.println("Worker " + workerId + " has finished accessing the shared resource.");
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
semaphore.release();
}
}
}
}
在上述示例中,我們創建了5個Worker線程,每個線程都會嘗試獲取一個信號量通路(acquire方法),如果信號量計數爲0,則線程將被阻塞。當線程成功獲取到通路後,它會打印訪問共享資源的消息,並模擬對共享資源的訪問操作。訪問完成後,線程會釋放信號量通路(release方法),以便其他線程可以繼續訪問共享資源。
通過運行上述示例,您可以觀察到只有兩個線程同時訪問共享資源,其他線程需要等待釋放通路後才能訪問。
使用Semaphore可以靈活控制線程之間的同步和互斥,使多個線程能夠安全地訪問共享資源,避免競態條件和數據不一致的問題。但要注意,信號量的計數應根據實際需求進行適當的設置,以避免死鎖或資源浪費的情況發生。