Semaphore 通常用於限制可以訪問某些資源(物理或邏輯的)的線程數目。信號量維護了一個許可集。如有必要,在許可可用前會阻塞每一個 acquire(),然後再獲取該許可。每個 release() 添加一個許可,從而可能釋放一個正在阻塞的獲取者。
Sync類
abstract static class Sync extends AbstractQueuedSynchronizer {
private static final long serialVersionUID = 1192457210091910933L;
// 許可總數量,使用AQS的state來保存
Sync(int permits) {
setState(permits);
}
// 獲取當前剩餘許可數量
final int getPermits() {
return getState();
}
// 非公平的獲取指定數量的許可,返回剩餘的數量
final int nonfairTryAcquireShared(int acquires) {
for (;;) {
int available = getState();
int remaining = available - acquires;
// 這裏剩餘的如果小於0,會造成短路,並沒有CAS更新的操作
if (remaining < 0 ||
compareAndSetState(available, remaining))
return remaining;
}
}
// 釋放指定個數的許可,將其返回給信號量。
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;
}
}
// 削減指定個數的許可數量
final void reducePermits(int reductions) {
for (;;) {
int current = getState();
int next = current - reductions;
if (next > current) // underflow
throw new Error("Permit count underflow");
if (compareAndSetState(current, next))
return;
}
}
// 獲取並返回立即可用的所有許可
final int drainPermits() {
for (;;) {
int current = getState();
if (current == 0 || compareAndSetState(current, 0))
return current;
}
}
}
NonfairSync
非公平版本的 Sync 。
static final class NonfairSync extends Sync {
private static final long serialVersionUID = -2694183684443567898L;
NonfairSync(int permits) {
super(permits);
}
protected int tryAcquireShared(int acquires) {
return nonfairTryAcquireShared(acquires);
}
}
FairSync
非公平的 Sync 當然要判斷隊列頭節點正在等待的是不是當前線程,如果不是的話當前線程要排隊等待。
static final class FairSync extends Sync {
private static final long serialVersionUID = 2014338818796000944L;
FairSync(int permits) {
super(permits);
}
protected int tryAcquireShared(int acquires) {
for (;;) {
if (hasQueuedPredecessors())
return -1;
int available = getState();
int remaining = available - acquires;
if (remaining < 0 ||
compareAndSetState(available, remaining))
return remaining;
}
}
}
構造函數
// 默認的構造函數是非公平的
public Semaphore(int permits) {
sync = new NonfairSync(permits);
}
// 給定訪問許可總數來構造公平或非公平的信號量
public Semaphore(int permits, boolean fair) {
sync = fair ? new FairSync(permits) : new NonfairSync(permits);
}
重要方法
acquire()
從此信號量獲取一個許可,在提供一個許可前一直將線程阻塞,否則線程被中斷。
public void acquire() throws InterruptedException {
sync.acquireSharedInterruptibly(1);
}
acquireUninterruptibly()
從此信號量中獲取許可,在有可用的許可前將其阻塞。
public void acquireUninterruptibly() {
sync.acquireShared(1);
}
tryAcquire()
僅在調用時此信號量存在一個可用許可,才從信號量獲取許可。
nonfairTryAcquireShared 方法如上邊展示的,剩餘爲負數時,並不更新 state 的值。說明此次獲取是失敗的。
public boolean tryAcquire() {
// 調用Sync中的方法,如果獲取1個資源後剩餘的大於等於0,返回true,如果剩餘的爲負數,返回false。
return sync.nonfairTryAcquireShared(1) >= 0;
}
release()
釋放一個許可,將其返回給信號量。
public void release() {
sync.releaseShared(1);
}
acquire(int permits)
從此信號量獲取給定數目的許可,在提供這些許可前一直將線程阻塞,或者線程已被中斷。
public void acquire(int permits) throws InterruptedException {
if (permits < 0) throw new IllegalArgumentException();
sync.acquireSharedInterruptibly(permits);
}
acquireUninterruptibly(int permits)
從此信號量獲取給定數目的許可,在提供這些許可前一直將線程阻塞。
public void acquireUninterruptibly(int permits) {
if (permits < 0) throw new IllegalArgumentException();
sync.acquireShared(permits);
}
tryAcquire(int permits)
僅在調用時此信號量中有給定數目的許可時,才從此信號量中獲取這些許可。
public boolean tryAcquire(int permits) {
if (permits < 0) throw new IllegalArgumentException();
return sync.nonfairTryAcquireShared(permits) >= 0;
}
release(int permits)
釋放給定數目的許可,將其返回到信號量。
public void release(int permits) {
if (permits < 0) throw new IllegalArgumentException();
sync.releaseShared(permits);
}
availablePermits()
返回此信號量中當前可用的許可數。
public int availablePermits() {
return sync.getPermits();
}
drainPermits()
獲取並返回立即可用的所有許可。
public int drainPermits() {
return sync.drainPermits();
}
reducePermits(int reduction)
根據指定的縮減量減小可用許可的數目。
protected void reducePermits(int reduction) {
if (reduction < 0) throw new IllegalArgumentException();
sync.reducePermits(reduction);
}
使用情景
我們以銀行辦業務的例子來說明,假設銀行有 4 個櫃檯,一次進來了 10 個辦理業務的人:
public class TestSemaphore {
private static Semaphore semaphore;
// 銀行櫃檯
public static class BankCounter {
// 辦業務
public void banking() throws InterruptedException{
semaphore.acquire();
System.out.println("用戶" + Thread.currentThread().getName() + "開始處理!");
// 處理業務的時間
int waitTime = (int)(Math.random()*10 +1);
Thread.sleep(waitTime*1000);
System.out.println("用戶" + Thread.currentThread().getName() + "處理結束,共用時 "+ waitTime +" 秒,離開窗口,下一個!");
semaphore.release();
}
}
// 等待窗口辦業務的線程
public static class WindowThread extends Thread{
@Override
public void run() {
try {
new BankCounter().banking();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
public static void main(String[] args) throws InterruptedException {
// 信號量設置爲4,等於只有4個窗口資源,設置爲公平模式排隊
semaphore = new Semaphore(4, true);
// 啓動10個線程,10個用戶在等待辦業務,500ms延遲是爲了線程編號從小到大公平的排隊
for(int i=0; i<10; i++){
new WindowThread().start();
Thread.sleep(500);
}
}
}
運行結果:
用戶Thread-0開始處理!
用戶Thread-1開始處理!
用戶Thread-2開始處理!
用戶Thread-3開始處理!
用戶Thread-1處理結束,共用時 2 秒,離開窗口,下一個!
用戶Thread-4開始處理!
用戶Thread-0處理結束,共用時 4 秒,離開窗口,下一個!
用戶Thread-5開始處理!
用戶Thread-2處理結束,共用時 4 秒,離開窗口,下一個!
用戶Thread-6開始處理!
用戶Thread-3處理結束,共用時 7 秒,離開窗口,下一個!
用戶Thread-7開始處理!
用戶Thread-4處理結束,共用時 7 秒,離開窗口,下一個!
用戶Thread-8開始處理!
用戶Thread-6處理結束,共用時 6 秒,離開窗口,下一個!
用戶Thread-9開始處理!
用戶Thread-9處理結束,共用時 1 秒,離開窗口,下一個!
用戶Thread-5處理結束,共用時 9 秒,離開窗口,下一個!
用戶Thread-7處理結束,共用時 8 秒,離開窗口,下一個!
用戶Thread-8處理結束,共用時 7 秒,離開窗口,下一個!