Semaphore通常用於在程序中做限流使用,控制一段程序同時只能有n個線程同時訪問。
既然是同時有多個線程能同時訪問,那Semaphore是用的肯定就是共享鎖。其原理就是有線程獲取了鎖,就對state減,釋放鎖後就對state加。state小於0的時候,就對線程阻塞。一旦釋放了鎖,就喚醒等待線程。
Semaphore是通過共享鎖實現的,如果對ReentrantReadWriteLock不熟悉的,請先看ReentrantReadWriteLock源碼分析。弄明白了共享鎖,理解Semaphore就很簡單了。
Semaphore創建
//默認使用的是非公平鎖
public Semaphore(int permits) {
sync = new NonfairSync(permits);
}
//傳入的permits保存在state中
Sync(int permits) {
setState(permits);
}
semaphore.acquire
public final void acquireSharedInterruptibly(int arg)
throws InterruptedException {
if (Thread.interrupted())
throw new InterruptedException();
//修改state的值,返回值爲還剩餘的state,如果小於0,說明state已經用完,則需要將線程掛起
if (tryAcquireShared(arg) < 0)
doAcquireSharedInterruptibly(arg);
}
//tryAcquireShared非公平鎖的實現。
//非公平鎖允許後來的線程直接獲取鎖。公平鎖的實現,判斷如果等待隊列中有等待線程,就不允許後來的線程插隊獲取鎖。
final int nonfairTryAcquireShared(int acquires) {
//每來一個線程,就將state的值做減法,返回值爲還剩餘的數量
//如果CAS失敗了,說明有其他線程正在搶佔,則繼續下一次的循環再次嘗試
for (;;) {
int available = getState();
int remaining = available - acquires;
if (remaining < 0 ||
compareAndSetState(available, remaining))
return remaining;
}
}
//將線程封裝爲Node節點並掛起
private void doAcquireSharedInterruptibly(int arg)
throws InterruptedException {
final Node node = addWaiter(Node.SHARED);
boolean failed = true;
try {
for (;;) {
final Node p = node.predecessor();
if (p == head) {
int r = tryAcquireShared(arg);
if (r >= 0) {
//獲取鎖之後,會鏈式繼續喚醒下一個節點
setHeadAndPropagate(node, r);
p.next = null; // help GC
failed = false;
return;
}
}
if (shouldParkAfterFailedAcquire(p, node) &&
parkAndCheckInterrupt())
throw new InterruptedException();
}
} finally {
if (failed)
cancelAcquire(node);
}
}
semaphore.release
public final boolean releaseShared(int arg) {
//如果釋放鎖成功了,就喚醒線程
if (tryReleaseShared(arg)) {
doReleaseShared();
return true;
}
return false;
}
protected final boolean tryReleaseShared(int releases) {
//這裏釋放鎖,和獲取鎖的邏輯是相反的,這裏對state最加法,成功了就返回true,就會喚醒等地啊隊列中的線程。
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;
}
}