本文接着分析Semaphore的实现原理
Semaphore是什么
Semaphore是一个计数信号量。Semaphore(信号)可以理解为一种许可,拿到许可的线程才可以继续执行。Semaphore的计数器其实记录的就是许可的数量,当许可数量为0时,acquire方法就会阻塞。这个系统和停车位系统非常相似,当停车场还有空位的时候,任何新来的车辆都可以进,当停车场满的时候,新来的车辆必须要等到有空车位产生的时候才可以开进停车场。这里的停车位就相当于Semaphore的许可数量。
Semaphore的使用方法
public static void main(String[] args) throws InterruptedException{
Semaphore semaphore = new Semaphore(3);
for (int i = 1; i <= 5; i++) {
final int threadNum = i;
new Thread(() -> {
try {
semaphore.acquire();
System.out.println("thread" + threadNum + ":entered");
Thread.sleep(1000 * threadNum);
System.out.println("thread" + threadNum + ":gone");
semaphore.release();
} catch (InterruptedException e) {
System.out.println("thread" + threadNum + ":exception");
}
}).start();
}
}
看一下输出结果:
thread2:entered
thread3:entered
thread1:entered
thread1:gone
thread4:entered
thread2:gone
thread5:entered
thread3:gone
thread4:gone
thread5:gone
Process finished with exit code 0
首先我们new了一个信号量,给了3个许可,然后在新建5个线程抢占信号量。一开始1,2,3号线程可以拿到许可,然后4号线程来了,发现没有许可了,4号线程阻塞,直到1号线程调用了release后有了许可后,4号线程被唤醒,以此类推。。。
Semaphore原理
Semaphore和Reentrant一样分别实现了公平锁和非公平锁,同样我们看非公平锁。上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))
//返回remaining,小于0表示许可数量不够,大于0表示许可数量足够
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;
}
}
}
了解了Semaphore对state的定义后,我们看一下acquire方法,该方法直接调用了sync的acquireSharedInterruptibly:
public final void acquireSharedInterruptibly(int arg)
throws InterruptedException {
//线程中断标志为true,抛出中断异常
if (Thread.interrupted())
throw new InterruptedException();
//首先尝试获取需要的许可数量
if (tryAcquireShared(arg) < 0)
//当获取失败
doAcquireSharedInterruptibly(arg);
}
doAcquireSharedInterruptibly在CountdownLatch里分析过:当获取许可失败时,往等待队列添加当前线程的node,如果队列没有初始化则初始化。然后在一个loop里从队列head后第一个node开始尝试获取许可,为了不让CPU空转,当head后第一个node尝试获取许可失败的时候,阻塞当前线程,第一个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);
}
}
当调用了release方法后,意味着有新的许可被释放,调用sync的releaseShared,接着调用Semaphore的内部类Sync实现的tryReleaseShared尝试释放许可。释放成功后调用AQS的doReleaseShared,在CountdownLatch中也见过这个方法。在之前head后第一个node线程阻塞之前,已经将head状态设置为SIGNAL,所以会唤醒第一个node的线程,该线程继续执行之前的loop,尝试获取许可成功,并且当还有剩余的许可存在时向后传播唤醒信号,唤醒后继node的线程,获取剩余的许可。
总结
Semaphore和CountdownLatch一样使用了AQS的共享模式;
Semaphore在有许可释放时唤醒第一个node的线程获取许可,之后会根据是否还存在许可来决定是否继续往后传播唤醒线程的信号。
CountdownLatch在state为0的时候依次往后传播唤醒信号,一直传播到低,直到所有线程被唤醒。
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。