本篇开始分析Semaphore(信号量)的源码,分析结束后,会用一个示例展示Semaphore的应用场景。
Semaphore是一个计数信号量,维护了一个信号量许可集。每次调用acquire()都将消耗一个许可,每次调用release()都将归还一个许可。
Semaphore的内部维护了一个Sync内部类,Sync是继承AQS的抽象类,Sync包括两个子类:"公平信号量"FairSync 和 "非公平信号量"NonfairSync。
public Semaphore(int permits) {
//默认构造非公平信号量
sync = new NonfairSync(permits);
}
public Semaphore(int permits, boolean fair) {
sync = fair ? new FairSync(permits) : new NonfairSync(permits);
}
Semaphore中的方法源码:
public void acquire() throws InterruptedException {
sync.acquireSharedInterruptibly(1);
}
public void acquire(int permits) throws InterruptedException {
if (permits < 0) throw new IllegalArgumentException();
sync.acquireSharedInterruptibly(permits);
}
sync中调用的acquireSharedInterruptibly方法在AQS中,源码如下:
public final void acquireSharedInterruptibly(int arg)
throws InterruptedException {
// 如果线程是中断状态,则抛出异常。
if (Thread.interrupted())
throw new InterruptedException();
// 尝试获取“共享锁”;获取成功则直接返回,获取失败,则通过doAcquireSharedInterruptibly()获取
if (tryAcquireShared(arg) < 0)
doAcquireSharedInterruptibly(arg);
}
tryAcquireShared方法调用的是FairSync中的方法,源码如下:
protected int tryAcquireShared(int acquires) {
for (;;) {
// 判断当前队列前面还有没有等待的线程
// 若有的话,则返回-1。
if (hasQueuedPredecessors())
return -1;
// 设置“可以获得的信号量的许可数”
int available = getState();
// 设置剩余的信号量许可数
int remaining = available - acquires;
// 如果“剩余的信号量许可数>=0”,则设置“可以获得的信号量许可数”为remaining。
//如果“剩余的信号量许可数小于0”,则进入doAcquireSharedInterruptibly方法
if (remaining < 0 ||
compareAndSetState(available, remaining))
return remaining;
}
}
下面看看AQS中doAcquireSharedInterruptibly()的源码实现,此方法在ReentrantReadWriteLock中的doAcquireShared也分析过,再看看:
private void doAcquireSharedInterruptibly(long arg)
throws InterruptedException {
// 创建”当前线程“的Node节点,且Node中记录的锁是”共享锁“类型;并将该节点添加到CLH队列末尾。
final Node node = addWaiter(Node.SHARED);
boolean failed = true;
try {
for (;;) {
// 当前节点的前一个节点。
final Node p = node.predecessor();
// 如果前一个节点是头节点(说明是第一个排队的节点)
if (p == head) {
// 再次尝试获取读锁
long 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中的方法源码:
public void release() {
sync.releaseShared(1);
}
public void release(int permits) {
if (permits < 0) throw new IllegalArgumentException();
sync.releaseShared(permits);
}
AQS中的releaseShared方法,源码如下:
public final boolean releaseShared(int arg) {
if (tryReleaseShared(arg)) {
doReleaseShared();
return true;
}
return false;
}
Sync重写了tryReleaseShared(),它的源码如下:
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");
// 如果原子更新state的值成功,就说明释放许可成功,则返回true
if (compareAndSetState(current, next))
return true;
}
}
如果tryReleaseShared()尝试释放共享锁失败,则会调用doReleaseShared()去释放共享锁。doReleaseShared()的源码如下:
//此方法只会唤醒一个节点
//和ReentrantReadWriteLock中代码一样,不再解释
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;
}
}
公平信号量和非公平信号量的释放是一样的,下面是非公平信号量的获取源码:
protected int tryAcquireShared(int acquires) {
return nonfairTryAcquireShared(acquires);
}
final int nonfairTryAcquireShared(int acquires) {
for (;;) {
//这里和公平信号量相比,少了一个判断
//少了的判断是:判断当前队列前面还有没有等待的线程
//这里就体现了公平和非公平的区别
int available = getState();
int remaining = available - acquires;
if (remaining < 0 ||
compareAndSetState(available, remaining))
return remaining;
}
}
通过上面的分析,我们知道了Semaphore的内部维护了一个字段state,用来限制同一时间对共同资源的访问次数,是不是非常像限流,现在我们就模拟限流场景,假设有1000个请求同时进来,我们只处理100个请求,防止服务器压力过大,代码如下:
public class SemaphoreLimit {
public static final Semaphore SEMAPHORE = new Semaphore(100);
private static final AtomicInteger SUCCESS = new AtomicInteger();
public static void main(String[] args) {
for (int i=0; i<1000; i++){
new Thread(() -> {
method();}).start();
}
System.out.printf("success request number=%d \n",SUCCESS.get());
}
public static void method(){
if(!SEMAPHORE.tryAcquire()){
return;
}
//处理业务
try {
Thread.sleep(100);
SUCCESS.incrementAndGet();
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
SEMAPHORE.release();
}
}
}
本篇分析了Semaphore源码,了解了Semaphore限流的应用场景,下一篇将分析CountDownLatch。
如果本篇的内容对你有帮助,请点个赞再走,谢谢大家!