Semaphore信號量,可以控同時訪問的線程個數(shù),acquire() 失敗就等待,而 release() 釋放一個許可。
來看看例子:
public class SemaphoreTrain {
static class Worker extends Thread {
private int n;
private Semaphore semaphore;
public Worker(int n, Semaphore semaphore) {
this.n = n;
this.semaphore = semaphore;
}
@Override
public void run() {
try {
semaphore.acquire();
System.out.println("Worker num " + n + " use machine");
Thread.sleep(2000);
System.out.println("Worker num " + n + " stop use");
semaphore.release();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
public static void main(String[] args) {
int worker = 6; //工人數(shù)
int machine = 4; //機器數(shù)
Semaphore semaphore = new Semaphore(machine);
for (int i = 0; i < worker; i++) {
new Worker(i, semaphore).start();
}
}
}
Worker num 1 use machine
Worker num 3 use machine
Worker num 2 use machine
Worker num 0 use machine
Worker num 0 stop use
Worker num 1 stop use
Worker num 3 stop use
Worker num 2 stop use
Worker num 4 use machine
Worker num 5 use machine
Worker num 4 stop use
Worker num 5 stop use
只允許最多四個線程同時運行,其它的等待執(zhí)行線程release退出。
來看看源碼實現(xiàn):
private final Sync sync;
abstract static class Sync extends AbstractQueuedSynchronizer {
private static final long serialVersionUID = 1192457210091910933L;
Sync(int permits) {
setState(permits);
}
final int getPermits() {
return getState();
}
final int nonfairTryAcquireShared(int acquires) {
for (;;) {
int available = getState();
int remaining = available - acquires;
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;
}
}
}
這里說點我對線程安全的思考:同步狀態(tài)在AQS中是valatile的,確保了可見性,Doug Lea大神在設(shè)計JUC框架時采用了非鎖式的方法來確保線程安全,不排他也就是正在運行任務(wù)的線程也可能因為調(diào)度的原因被新的線程獲取到執(zhí)行資格,但是新的線程想要執(zhí)行任務(wù)是無法獲取到資格的,這個資格由同步狀態(tài)來實現(xiàn),它會被加到等待隊列中去。
以nonfairTryAcquireShared方法為例:A線程getState獲取到最新state個數(shù),B 線程搶到執(zhí)行資格更改了state,回到A當它想要計算remaining值時,A所緩存的state值會失效它會重新從內(nèi)存中獲取;這時又一線程搶過來更改了state,再回到A當它利用CAS更改state值時會執(zhí)行失敗,重新循環(huán)再來一遍,之前說過循環(huán)CAS就是Java的一種鎖實現(xiàn)方式,另一種是內(nèi)置鎖Synchronized。
非公平鎖
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);
}
}
公平鎖
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;
}
}
}
之前ReentrentLock里分析過,公平與非公平的不同就在對tryAcquire處理上,由于插隊可能發(fā)生在兩個地方,一個是頭節(jié)點release歸零了同步狀態(tài)還未喚醒后繼節(jié)點;另一處是后繼節(jié)點被喚醒,在acquireQueued的for循環(huán)判斷里;這兩處都會嘗試tryAcquire,存在被剛acquire的線程插隊的情況。
而解決辦法就是判斷等待隊列中是否已有正在等待的線程,對于公平鎖來說有那么你就直接被pass掉,輪不到你。
構(gòu)造函數(shù)
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);
}
//-----------------------------方法在AQS中
public final void acquireSharedInterruptibly(int arg)
throws InterruptedException {
if (Thread.interrupted())
throw new InterruptedException();
//小于零說明沒有獲得許可,需要等待,對于公平鎖來說沒輪到你就直接返回-1,邏輯在hasQueuedPredecessors里
if (tryAcquireShared(arg) < 0)
doAcquireSharedInterruptibly(arg);
}
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;
}
}
//這里shouldParkAfterFailedAcquire會將前面節(jié)點的狀態(tài)改為Signal
//對于等待隊列中節(jié)點的waitStatus初始為0,之后由后加入的節(jié)點改為SIGNAL
//SIGNAL這個狀態(tài)它表明你有后繼節(jié)點,release時喚醒它
if (shouldParkAfterFailedAcquire(p, node) &&
parkAndCheckInterrupt())
throw new InterruptedException();
}
} finally {
if (failed)
cancelAcquire(node);
}
}
release
public void release() {
sync.releaseShared(1);
}
public final boolean releaseShared(int arg) {
if (tryReleaseShared(arg)) {
doReleaseShared();
return true;
}
return false;
}
private void doReleaseShared() {
for (;;) {
Node h = head;
if (h != null && h != tail) {
int ws = h.waitStatus;
if (ws == Node.SIGNAL) {
if (!compareAndSetWaitStatus(h, Node.SIGNAL, 0))
continue; // loop to recheck cases
unparkSuccessor(h);
}
else if (ws == 0 &&
!compareAndSetWaitStatus(h, 0, Node.PROPAGATE))
continue; // loop on failed CAS
}
if (h == head) // loop if head changed
break;
}
}
來說說doReleaseShared,前面在說shouldParkAfterFailedAcquire時說過SIGNAL節(jié)點表明你有后繼節(jié)點,那么當你的waitStatus == 0時代表等待隊列中沒有等待的線程。那么這種情況下就將它改為PROPAGATE狀態(tài),之后在進入等待隊列中的節(jié)點會將它改為SIGNAL,這也表明HEAD不一定代表正在運行的線程,我想HEAD就是一個頭的標識,每個節(jié)點的狀態(tài)代表了release時要進行的處理。
總結(jié)
Semaphore維持了一個同步狀態(tài)state(大小初始化時設(shè)定,代表最大允許執(zhí)行線程數(shù)),在acquire時將state減1,小于零加入同步隊列等待,大于零則CAS更改值。
由于Semaphore與之前的同步器在實現(xiàn)原理上沒什么不同,所以本具體每一步驟就不介紹了,只說了說我在看它源碼時的一些思考。