版本
JDK8(JDK1.8)
Semaphore类源码重点
-
Semaphore(信号量)类有三个内部类Sync、NonfairSync、FairSync都继承自AQS(AbstractQueuedSynchronizer)
AQS 源码可以看我这篇文章 AbstractQueuedSynchronizer -
AQS里面的state变量在不同AQS实现类有不同的意义,如在ReentrantLock中代表获取锁的次数,在Semaphore表示当前许可证的数量,即只有获取许可证的线程才可以执行自己的业务代码,否则只能进入AQS队列进行排队,而这恰好就是 Semaphore实现限流的原理
ReentrantLock 源码可以看我这篇文章 ReentrantLock -
Semaphore实现限流的原理,假设一开始将AQS的state设为10即10个许可证,每个线程想要执行自己的代码先去获取许可证,获取一个许可证state数量就减一,减到0后如果还有线程想获取许可证,那是获取不到的,只能进去AQS的队列进行排队,直到排队排到队头才尝试获取许可证,当有获取许可证的线程执行完代码会释放许可证
-
AQS虚拟类是双向链表实现的,链表中每个结点都对应一个线程,除了队头的线程是运行的,其他所有结点的线程几乎都是阻塞的,所以这就是叫阻塞阻塞队列的原因
Semaphore类具体实现
查询操作
availablePermits操作
作用:获取当前许可证的数量
public int availablePermits() {
return sync.getPermits();
}
sync.getPermits操作
调用AQS的getState方法,获取AQS的state的值,state变量在不同AQS实现类有不同的意义,如在ReentrantLock中代表获取锁的次数,在Semaphore表示当前许可证的数量,即只有获取许可证的线程才可以执行自己的业务代码,否则只能进入AQS队列进行排队
final int getPermits() {
return getState();
}
修改操作
reducePermits操作
作用:减少同时并发的线程数量
实现:减少AQS的state的值,state是当前许可证的数量,也可以理解为还能够让多少个线程并发,有一个线程获取许可证则会让state值减一,如果一开始state为10,那么刚好可以供10个线程并发,如果state为0了,只能等待并发的线程释放许可证才能有新线程能获取许可证
protected void reducePermits(int reduction) {
if (reduction < 0) throw new IllegalArgumentException();
sync.reducePermits(reduction);
}
drainPermits操作
作用:清空所有许可证
实现:调用drainPermits自旋直到使用CAS将state设为0,或state本来就为0才退出自旋
public int drainPermits() {
return sync.drainPermits();
}
sync.drainPermits操作
调用getState获取AQS的state的值,使用CAS将其设为0,CAS成功才返回,否则自旋
final int drainPermits() {
for (;;) {
int current = getState();
if (current == 0 || compareAndSetState(current, 0))
return current;
}
}
Acquire操作(获取许可证)
acquire操作
作用:尝试获取许可证,没有成功则进入阻塞队列AQS排队
实现:使用AQS虚拟类的acquireSharedInterruptibly方法实现,传入的1是请求的许可证数量
public void acquire() throws InterruptedException {
sync.acquireSharedInterruptibly(1);
}
acquireSharedInterruptibly
- 先判断是否有中断标志,如果有直接抛出异常中断线程
- 使用tryAcquireShared尝试获取许可证,如果没有成功,则执行doAcquireSharedInterruptibly,把当前线程当作结点插入AQS阻塞队列进行自旋排队
- 只有排队到队头才会重新尝试获取许可证,且每自旋一次(即循环一次)都会阻塞当前线程,同时每循环一次会检测该线程是否被打上了中断信号,是则中断
- tryAcquireShared是实现AQS子类需要实现的方法,作用是尝试获取共享锁,但它有两种实现一种公平锁,一种非公平锁,是怎样决定用哪个实现的呢,就在于上面acquire()方法的sync是 FairSync内部类的对象 还是 NonfairSync内部类的对象
public final void acquireSharedInterruptibly(int arg)
throws InterruptedException {
if (Thread.interrupted())
throw new InterruptedException();
// 尝试获取许可证,如果没有成功
if (tryAcquireShared(arg) < 0)
// 自旋排队
doAcquireSharedInterruptibly(arg);
}
tryAcquireShared
- hasQueuedPredecessors判断AQS阻塞队列中有无线程在排队,如果有返回-1,获取失败,所以是公平锁
- getState()获取AQS中的state变量,state变量在不同AQS实现类有不同的意义,如在ReentrantLock中代表获取锁的次数,在Semaphore表示当前许可证的数量,即只有获取许可证的线程才可以执行自己的业务代码,否则只能进入AQS队列进行排队
- 如果剩余许可证数量remaining大于等于0,说明获取许可证成功可以执行业务代码,如果remaining小于0就是获取许可证失败可以直接跳出自旋(即循环)
- 使用CAS更新当前许可证的数量available为剩余许可证数量remaining,如果成功则跳出循环,不成功则重新执行逻辑
- 注意重新执行逻辑时会重新获取当前许可证的数量available,所以不是累减操作
公平锁版本
protected int tryAcquireShared(int acquires) {
for (;;) {
// 如果有线程在排队
if (hasQueuedPredecessors())
return -1;
// 获取当前许可证的数量
int available = getState();
// 获取许可证剩余的数量
int remaining = available - acquires;
// 如果剩余许可证
if (remaining < 0 ||
// CAS更新当前许可证的数量为剩余许可证数量
compareAndSetState(available, remaining))
// 跳出自旋
return remaining;
}
}
非公平锁版本
由于是非公平版本所以无须判断AQS阻塞队列中有无线程在排队,直接使用自己实现的方法,但是AQS的state值还是要使用的,因为当前许可证的数量存储在state中
protected int tryAcquireShared(int acquires) {
return nonfairTryAcquireShared(acquires);
}
nonfairTryAcquireShared操作
- 获取当前许可证的数量,计算获取许可证后剩余许可证的数量,和公平锁版本区别在于,无须使用hasQueuedPredecessors判断是否有线程在排队
- 其他和公平锁版本一模一样
final int nonfairTryAcquireShared(int acquires) {
for (;;) {
// 获取当前许可证的数量
int available = getState();
// 获取剩余许可证的数量
int remaining = available - acquires;
if (remaining < 0 ||
// CAS更新当前许可证的数量为剩余许可证数量
compareAndSetState(available, remaining))
// 返回剩余许可证的数量
return remaining;
}
}
acquire(int permits)操作
作用:尝试获取多个许可证,没有成功则进入阻塞队列AQS排队
实现:使用AQS虚拟类的acquireSharedInterruptibly方法实现,传入的permits是请求的许可证数量
和acquire()的区别只在于acquire()只获取一个许可证,而acquire(int permits)一次获取permits个许可证
public void acquire(int permits) throws InterruptedException {
if (permits < 0) throw new IllegalArgumentException();
sync.acquireSharedInterruptibly(permits);
}
acquireUninterruptibly操作
作用:尝试获取1个许可证,没有成功则进入阻塞队列AQS排队
实现:使用AQS虚拟类的acquireShared方法实现,传入的1是请求的许可证数量,虽然叫Uninterruptibly不可中断,但其实AQS内部acquireShared还是会判断是否中断标志,有则自我中断
public void acquireUninterruptibly() {
sync.acquireShared(1);
}
acquireShared操作
- tryAcquireShared尝试获取共享锁,有两种实现公平锁和非公平锁,上面讲过的,如果获取锁没有成功则执行doAcquireShared
- doAcquireShared方法将该线程作为节点添加进AQS同步队列排队,同时自旋(死循环)一直判断当前节点是否排队到队头了,到了则重新尝试获取共享锁,没有到或获取失败则阻塞当前线程,等待前一个节点的唤醒,AQS队列中前一个结点负责唤醒后一个结点的线程,只有队头获取到锁,当队头释放锁的时候会唤醒后一个线程去抢锁
public final void acquireShared(int arg) {
//尝试获取共享锁
if (tryAcquireShared(arg) < 0)
doAcquireShared(arg);
}
acquireUninterruptibly(int permits)操作
作用:尝试获取permits个许可证,没有成功则进入阻塞队列AQS排队
实现:使用AQS虚拟类的acquireShared方法实现,传入的permits是请求的许可证数量,和acquireUninterruptibly()唯一的区别就是,acquireUninterruptibly()只获取一个许可证,而它获取 permits个许可证
public void acquireUninterruptibly(int permits) {
if (permits < 0) throw new IllegalArgumentException();
sync.acquireShared(permits);
}
Release操作(释放许可证)
release操作
作用:释放许可证,1表示释放的许可证的数量
实现:调用AQS的releaseShared进行释放操作
public void release() {
sync.releaseShared(1);
}
releaseShared操作
- 使用tryReleaseShared尝试释放arg数量的许可证,如果释放成功,则执行doReleaseShared唤醒AQS队列后续一个结点
- 唤醒的结点会去尝试去获取许可证再执行自己的业务逻辑,因为每个结点对应一个线程
- tryReleaseShared是AQS子类自己实现的
public final boolean releaseShared(int arg) {
if (tryReleaseShared(arg)) {
doReleaseShared();
return true;
}
return false;
}
tryReleaseShared操作
- 获取当前许可证的数量current,计算释放许可证后的许可证的数量next,如果释放后许可证数量反而减少则抛出异常
- 使用CAS更新当前许可证的数量state为新的值next,如果更新成功跳出自旋,返回成功,如果没有成功则自旋直到成功为止
- CAS失败的原因,因为在代码执行过程中其他线程获取许可证或释放许可证改变了state的值,使得state不等于current
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;
}
}
release(int permits)操作
作用:释放permits个许可证
实现:和release操作几乎一模一样
public void release(int permits) {
if (permits < 0) throw new IllegalArgumentException();
sync.releaseShared(permits);
}