JUC 之 Semaphore 信号量
注意:学习本篇内容之前请先阅读【AQS 之 共享锁 源码剖析】一篇,AQS 是基础。
在某些场景中我们需要控制接口或某些方法在某一时刻的访问次数,防止一下涌入大量请求压垮我们的服务,Java 信号量就能完成该功能。Semaphore 维护了一组许可,每次访问接口或方法前必须先获得许可(访问凭证),拿到凭证才有访问权限,不然将被阻塞。接下来我们就来看看具体实现。
进入源码之前我们先来看个使用示例:
static class SemaphoreDemo {
private static final int MAX_AVAILABLE = 100;
// 创建一个 Semaphore 对象,拥有 100 个访问许可(资源池)
private final Semaphore semaphore = new Semaphore(MAX_AVAILABLE);
public Object getItem() throws InterruptedException {
semaphore.acquire();// 获取许可
return getNextAvailableItem();// 拿到许可后执行业务程序
}
public void putItem(Object x) {
if (markAsUnused(x)) semaphore.release();// 释放自己持有的资源(还回资源池)
}
// 以下代码属于业务代码
protected Object[] items = new Object[MAX_AVAILABLE];
protected boolean[] used = new boolean[MAX_AVAILABLE];
protected synchronized Object getNextAvailableItem() {
for (int i = 0; i < MAX_AVAILABLE; ++i) {
if (!used[i]) {
used[i] = true;
return items[i];
}
}
return null;
}
protected synchronized boolean markAsUnused(Object item) {
for (int i = 0; i < MAX_AVAILABLE; ++i) {
if (item == items[i]) {
if (used[i]) {
used[i] = false;
return true;
} else return false;
}
}
return false;
}
}
通过该示例代码我们发现 Semaphore 的核心就是 acquire 与 release 这两个方法。下面我们就进入 Semaphore 的源码之旅。
构造器
// 默认创建非公平锁机制
public Semaphore(int permits) {
sync = new NonfairSync(permits);
}
//根据 fair 选择创建 公平或非公平
public Semaphore(int permits, boolean fair) {
sync = fair ? new FairSync(permits) : new NonfairSync(permits);
}
Sync 内部类
通过下述源码我们发现在初始化 Sync 的时候提前将 AQS 的 state 变量初始化为我们传入的 permits 容量,线程在获取到锁就将 state 减 1,当 state 等于 0 时说明已经没有使用凭证了,再有线程请求时将交由 AQS 负责阻塞、唤醒。
abstract static class Sync extends AbstractQueuedSynchronizer {
Sync(int permits) {
setState(permits);// 初始化 可用资源数
}
final int getPermits() {
return getState();// 获取当前可用资源数
}
}
nonfairTryAcquireShared 方法
非公平的方式抢资源,将 state 减去 acquires(用户需要的资源数),当 state < 0 时,返回通知 AQS 获取锁失败,AQS 负责线程阻塞及唤醒操作。
final int nonfairTryAcquireShared(int acquires) {
for (;;) {
int available = getState();// 保存当前 state 值,当前可用资源数
int remaining = available - acquires;
if (remaining < 0 ||
compareAndSetState(available, remaining))// CAS 更新 state 值,
return remaining;
}
}
tryReleaseShared 方法
释放资源操作,将持有的资源数加回 state 中
protected final boolean tryReleaseShared(int releases) {
for (;;) {
int current = getState();// 保存当前 state 值,当前可用资源数
int next = current + releases; // 将 releases 加回 state 中
if (next < current) // overflow
throw new Error("Maximum permit count exceeded");
if (compareAndSetState(current, next))// CAS 更新 state 值
return true;
}
}
NonfairSync 内部类
非公平机制实现
static final class NonfairSync extends Sync {
NonfairSync(int permits) {
super(permits);// 调用 Sync 构造器初始化 state
}
// 调用 Sync 的 nonfairTryAcquireShared 方法获取凭证
protected int tryAcquireShared(int acquires) {
return nonfairTryAcquireShared(acquires);
}
}
FairSync 内部类
公平机制实现
static final class FairSync extends Sync {
FairSync(int permits) {
super(permits);// 调用 Sync 构造器初始化 state
}
protected int tryAcquireShared(int acquires) {
for (;;) {
// 不管资源池中是否有可用资源,优先判断 AQS 的 竞争队列中是否有排队节点,有,排队等待,否则去资源池中获取访问凭证
if (hasQueuedPredecessors())
return -1;
int available = getState();
int remaining = available - acquires;
if (remaining < 0 ||
compareAndSetState(available, remaining))
return remaining;
}
}
}
核心方法
acquire 方法
获取访问凭证,间接调用 AQS 提供的可中断获取共享锁的方法(acquireSharedInterruptibly) ,此方法在【AQS 之 共享锁 源码剖析】已经讲解过,此处不在赘述
public void acquire() throws InterruptedException {
sync.acquireSharedInterruptibly(1);
}
release 方法
释放凭证,归还资源池,调用 AQS 的 releaseShared 方法完成释放操作,此方法在【AQS 之 共享锁 源码剖析】已经讲解过,此处不在赘述
public void release() {
sync.releaseShared(1);
}
总结:利用 AQS 的共享锁机制,初始设置一个固定 state 值(初始容量),当有线程需要获取访问权时,检测 state 是否有可用资源,state > 0 代表有资源可用,获取到访问凭证后将 state 减 1;只要 state > 0 其他线程就可以获取访问凭证,直到 state = 0。利用该特性就可以完成对接口的限流操作,控制并发请求量。