Semaphore信号量,可以控同时访问的线程个数,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; //工人数
int machine = 4; //机器数
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
只允许最多四个线程同时运行,其它的等待执行线程release退出。
来看看源码实现:
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;
}
}
}
这里说点我对线程安全的思考:同步状态在AQS中是valatile的,确保了可见性,Doug Lea大神在设计JUC框架时采用了非锁式的方法来确保线程安全,不排他也就是正在运行任务的线程也可能因为调度的原因被新的线程获取到执行资格,但是新的线程想要执行任务是无法获取到资格的,这个资格由同步状态来实现,它会被加到等待队列中去。
以nonfairTryAcquireShared方法为例:A线程getState获取到最新state个数,B 线程抢到执行资格更改了state,回到A当它想要计算remaining值时,A所缓存的state值会失效它会重新从内存中获取;这时又一线程抢过来更改了state,再回到A当它利用CAS更改state值时会执行失败,重新循环再来一遍,之前说过循环CAS就是Java的一种锁实现方式,另一种是内置锁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处理上,由于插队可能发生在两个地方,一个是头节点release归零了同步状态还未唤醒后继节点;另一处是后继节点被唤醒,在acquireQueued的for循环判断里;这两处都会尝试tryAcquire,存在被刚acquire的线程插队的情况。
而解决办法就是判断等待队列中是否已有正在等待的线程,对于公平锁来说有那么你就直接被pass掉,轮不到你。
构造函数
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会将前面节点的状态改为Signal
//对于等待队列中节点的waitStatus初始为0,之后由后加入的节点改为SIGNAL
//SIGNAL这个状态它表明你有后继节点,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节点表明你有后继节点,那么当你的waitStatus == 0时代表等待队列中没有等待的线程。那么这种情况下就将它改为PROPAGATE状态,之后在进入等待队列中的节点会将它改为SIGNAL,这也表明HEAD不一定代表正在运行的线程,我想HEAD就是一个头的标识,每个节点的状态代表了release时要进行的处理。
总结
Semaphore维持了一个同步状态state(大小初始化时设定,代表最大允许执行线程数),在acquire时将state减1,小于零加入同步队列等待,大于零则CAS更改值。
由于Semaphore与之前的同步器在实现原理上没什么不同,所以本具体每一步骤就不介绍了,只说了说我在看它源码时的一些思考。