Semaphore(信号量)是控制访问线程的基于AQS的并发工具。也就是说使用Semaphore能够控制访问资源的并发量。比如在限流这块。
按照限流的逻辑,所以在使用的时候,需要进行设置最大的并发线程,按照我们之前对countDloadLatch的理解。通过设定总量,然后在条件满足之后进行下一工作。
semaphore则是通过设定最大的并发线程,在当前线程消耗的指定的掉总量之后。总量的剩余量与当初设定的总量进行对比,如果小于总量就排队,如果大于总量就进行阻塞。如果超过超时时间就剔除。
semaphore的大致逻辑就是这样。其中使用了AQS做基础的保证。
public class MySemaphore {
public static Semaphore semaphore=new Semaphore(2);
static class task implements Runnable{
@Override
public void run() {
try {
semaphore.acquire();
System.out.println(Thread.currentThread().getName());
Thread.sleep(500);
} catch (InterruptedException e) {
e.printStackTrace();
}finally {
semaphore.release();
}
}
}
public static void main(String[] args) {
ExecutorService executorService= Executors.newCachedThreadPool();
for (int i=0;i<10;i++){
executorService.submit(new task());
}
executorService.shutdown();
}
}
我们可以修改Semaphore的值,然后观察结果,能够很明显的观察到这种并发控制的效果。
下面我们从源码角度看一下他是如何实现的。
从图中可以看出,Seamaphore的结构和其他的并发工具一样,都有公平锁和非公平锁,一版这种情况都是和AQS关系很大,然后看到很多之前我们看到的方法。
下面我们从初始化方法和上述demo的层面上进行深入。
public Semaphore(int permits) {
sync = new NonfairSync(permits);
}
在初始化的时候,默认采用的是非公平锁。
public Semaphore(int permits, boolean fair) {
sync = fair ? new FairSync(permits) : new NonfairSync(permits);
}
或者自行决定是公平还是非公平锁
public void acquire() throws InterruptedException {
sync.acquireSharedInterruptibly(1);
}
当我们调用acquire()方法的时候,发现调用的是acquireSharedInterruptibly,我们跟踪一下。
doAcquireShared这个方法我们之前分析过,就是要从AQS链表中从后向前的寻找。剔除超时的,把能激活的线程向前移动,然后等待之前的额线程执行完毕之后抢夺head节点。
那么这块的意思就是说要先获取state的数量,然后看state减去申请的数量(这里是1),是否还有剩余,如果有剩余说明可以加入到队列。如果state没有剩余,那么就进行自旋,然后一直尝试往链表中添加。
按照这种逻辑,那么释放的时候,就应该是是对state的值进行相加。那么我们看看源码是如何实现的。
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;
}
}
果然,Semaphore就是对state的值进行加法操作。这样保证信号量的值的统一。
好了关于Semaphore的源码解析就到这里了。
总结:
Semaphore使用AQS进行信号量的实现。通过对state值得维护来实现信号量模式,在实际应用中,我们可以采用Semaphore来做系统的限流业务。