前面我已經講解過了CountDownLatch和CyclicBarrier。本篇我們來講解下Semaphore。
Semaphore是指信號量,在計算機的世界裏信號量可以使用在數據競爭的場景中。在生活中交通信號燈可以比作現實世界中的Semaphore。Semaphore的作用就是允許或者禁止。比如說紅燈禁止通行,綠燈允許通行。計算機世界裏的Semaphore會持有多張許可證,舉個例子有10張許可證,假設有20個線程同時請求信用量的許可證,那麼只能是其中十個線程能夠拿到許可證,執行代碼。另外10個線程只能等拿到許可證的線程釋放許可證。舉個生活中的例子。比如在銀行辦理業務,假設大廳有3個窗口。辦理業務的羣衆有10個。那麼每次只能有3個羣衆能獲得叫號的資格(對應計算機拿到許可證),如果有一個羣衆辦理完業務了,那麼窗口才能空餘出來(對應計算機線程釋放許可證),等待的羣衆才能獲得叫號的資格。
我們用代碼來模擬下該場景。假設只有3個窗口,有10個羣衆來辦理業務,每個業務辦理1~5秒鐘不等
public class Bank {
int[] windows = new int[3];//3個辦事窗口
final boolean[] busy = new boolean[3];//辦事窗口是否busy
String[] persons = new String[10];
Semaphore semaphore = new Semaphore(3);//最多隻有3張許可證
{
for (int i = 0; i < 10; i++) {
persons[i] = "person " + i;
}
}
ExecutorService pool = Executors.newCachedThreadPool();
public void deal() {
for (final String person : persons) {
pool.execute(new Runnable() {
public void run() {
try {
semaphore.acquire();
int index = 0;
synchronized (busy) {
for (int i = 0; i < 3; i++) {
if (!busy[i]) {//如果該窗口空閒
index = i;
busy[i] = true;
System.out.println("請 " + person + " 到 " + index + " 號窗口辦理業務");
break;
}
}
}
Random random = new Random();
int second = 1 + random.nextInt(5);
TimeUnit.SECONDS.sleep(second);
System.out.println(person + "在"+index+" 窗口辦理完成 費時" + second + "秒");
synchronized (busy) {
busy[index] = false;
}
} catch (
InterruptedException e
)
{
e.printStackTrace();
} finally
{
semaphore.release();
}
}
});
}
}
public static void main(String[] args) {
Bank bank = new Bank();
bank.deal();
}
}
輸出結果如下
請 person 0 到 0 號窗口辦理業務
請 person 1 到 1 號窗口辦理業務
請 person 2 到 2 號窗口辦理業務
person 1在1 窗口辦理完成 費時1秒
請 person 3 到 1 號窗口辦理業務
person 0在0 窗口辦理完成 費時3秒
請 person 4 到 0 號窗口辦理業務
person 2在2 窗口辦理完成 費時3秒
請 person 5 到 2 號窗口辦理業務
person 3在1 窗口辦理完成 費時2秒
請 person 6 到 1 號窗口辦理業務
person 4在0 窗口辦理完成 費時3秒
請 person 7 到 0 號窗口辦理業務
person 5在2 窗口辦理完成 費時4秒
請 person 8 到 2 號窗口辦理業務
person 6在1 窗口辦理完成 費時5秒
請 person 9 到 1 號窗口辦理業務
person 8在2 窗口辦理完成 費時2秒
person 7在0 窗口辦理完成 費時4秒
person 9在1 窗口辦理完成 費時3秒
從打印結果我們可以看出,每次最多允許3個人辦理業務,每當窗口空閒出來就會叫號。
接下來我們看看Semaphore源碼
public class Semaphore implements java.io.Serializable {
private static final long serialVersionUID = -3222578661600680210L;
/** All mechanics via AbstractQueuedSynchronizer subclass */
private final Sync sync;
abstract static class Sync extends AbstractQueuedSynchronizer {
}
}
Semaphore持有一個Sync對象,該對象是一個AQS。之前的併發系列我們講過AQS。它是同步的核心。而Lock類也是持有Sync對象。我們可以把Semaphore看成是一個Lock。區別是Semaphore沒有lock和unlock方法,取而代之的是acquire 和release方法
public void acquire() throws InterruptedException {
sync.acquireSharedInterruptibly(1);
}
public boolean tryAcquire(int permits) {
if (permits < 0) throw new IllegalArgumentException();
return sync.nonfairTryAcquireShared(permits) >= 0;
}
final int nonfairTryAcquireShared(int acquires) {
for (;;) {
int available = getState();
int remaining = available - acquires;
if (remaining < 0 ||
compareAndSetState(available, remaining))
return remaining;
}
}
看到share字樣,首先這把鎖是共享鎖。每次獲取到了鎖,許可證數量-1,如果許可證數量小於0,則獲取鎖失敗,該線程等待
public void release() {
sync.releaseShared(1);
}
protected final boolean tryReleaseShared(int releases) {
for (;;) {
int current = getState();
int next = current + releases;
if (next < current) // overflow
throw new Error("Maximum permit count exceeded");
if (compareAndSetState(current, next))
return true;
}
}
release方法許可證+1,同時通知等待線程,嘗試獲取許可證
總結下CountDownLatch CyclicBarrier Semaphore的區別
CountDownLatch 適合做彙總相關的工作,比如開啓多個線程爬取數據,在所有線程完成任務後,將數據做彙總。
CyclicBarrier適合於競賽相關的場景,讓多個線程在同一時間點執行任務。
Semaphore適用於資源有限的情況下的流量控制。