1. Semaphore是什么?
Semaphore(信号量)是用来控制同时访问特定资源的线程数量,它通过协调各个线程,以保证合理的使用公共资源。
Semaphore一般用于流量的控制,特别是公共资源有限的应用场景。例如数据库的连接,假设数据库的连接数上线为10个,多个线程并发操作数据库可以使用Semaphore来控制并发操作数据库的线程个数最多为10个。
2. 类图
通过类图可以看到,Semaphore与ReentrantLock的内部类的结构相同(ReentrantLock参考Lock与ReentrantLock_我不是欧拉_的博客-CSDN博客),类内部总共存在Sync、NonfairSync、FairSync三个类,NonfairSync与FairSync类继承自Sync类,Sync类继承自AbstractQueuedSynchronizer抽象类。
3. 实现原理
3.1 使用示例
// 定义一个资源池类
class Pool {
// 可用资源数100
private static final int MAX_AVAILABLE = 100;
// 定义信号量100
private final Semaphore available = new Semaphore(MAX_AVAILABLE, true);
// 获取资源
public Object getItem() throws InterruptedException {
// 尝试获取
available.acquire();
// 返回可用资源
return getNextAvailableItem();
}
// 释放资源
public void putItem(Object x) {
// 如果资源标记为未被使用
if (markAsUnused(x))
// 释放资源
available.release();
}
// Not a particularly efficient data structure; just for demo
// 定义资源类型,可以是满足业务的任何类型
protected Object[] items = new Object[MAX_AVAILABLE] ... whatever kinds of items being managed
// 是否被使用标记
protected boolean[] used = new boolean[MAX_AVAILABLE];
// 获取下一个可用资源
protected synchronized Object getNextAvailableItem() {
// 循环遍历
for (int i = 0; i < MAX_AVAILABLE; ++i) {
// 如果未被使用
if (!used[i]) {
// 使用标记设置为true
used[i] = true;
// 返回当前的资源
return items[i];
}
}
return null; // not reached
}
// 标记资源为未被使用
protected synchronized boolean markAsUnused(Object item) {
// 循环遍历
for (int i = 0; i < MAX_AVAILABLE; ++i) {
// 找到需要释放的资源
if (item == items[i]) {
// 如果是被使用中
if (used[i]) {
// 使用标记设置为false
used[i] = false;
// 返回true表示标记成功
return true;
} else
// 返回false表示标记失败
return false;
}
}
return false;
}
}
3.2 Sync
abstract static class Sync extends AbstractQueuedSynchronizer {
private static final long serialVersionUID = 1192457210091910933L;
// 构造方法,调用父类AQS的setState方法,给共享变量state赋值
// 即通过构造方法给锁的数量附初始值
Sync(int permits) {
setState(permits);
}
// 获取锁,也叫许可
final int getPermits() {
return getState();
}
// 共享模式下的非公平获取
// 此方法也体现出与ReentrantLock中Sync的实现不同
// ReentrantLock中Sync是独占模式下的获取
// 具体实现的不同体现在int remaining = available - acquires;
final int nonfairTryAcquireShared(int acquires) {
for (;;) {
// 获取锁的可用数量
int available = getState();
// 可用数量 - 请求的数量(acquires默认值为1) = 剩余量
int remaining = available - acquires;
// 如果remaining < 0即请求的锁大于可用的数量,马上返回负数,表示获取锁失败
if (remaining < 0 ||
// 否则通过CAS的方式将可用数量换成剩余量,并返回剩余量
// 自旋 + CAS 保证线程安全,线程不用排队体现出非公平性
compareAndSetState(available, remaining))
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");
// CAS修改锁数量,成功则返回,失败则继续自旋
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;
}
}
}
3.3 NonfairSync
static final class NonfairSync extends Sync {
private static final long serialVersionUID = -2694183684443567898L;
// 构造方法初始化锁数量
NonfairSync(int permits) {
super(permits);
}
// 直接调用nonfairTryAcquireShared方法,走非公平策略
protected int tryAcquireShared(int acquires) {
return nonfairTryAcquireShared(acquires);
}
}
3.4 FairSync
static final class FairSync extends Sync {
private static final long serialVersionUID = 2014338818796000944L;
// 构造方法初始化锁数量
FairSync(int permits) {
super(permits);
}
// 共享模式下的公平策略获取
// 与非公平策略唯一的不同体现在线程是否需要排队
// 即是否调用hasQueuedPredecessors()方法进行判断
// 如果需要排队则立即返回继续排队
// 否则通过CAS方式获取锁并返货锁的剩余量,结束自旋
protected int tryAcquireShared(int acquires) {
for (;;) {
if (hasQueuedPredecessors())
return -1;
int available = getState();
int remaining = available - acquires;
if (remaining < 0 ||
compareAndSetState(available, remaining))
return remaining;
}
}
}
通过分析代码发现,Semaphore与ReentrantLock的内部类的结构相同,具体实现的不同体现在 int remaining = available - acquires这行代码上。
ReentrantLock对于锁的控制是 int c = getState(); if (c == 0){....}。体现为一种独占的控制。
Semaphore对锁的控制是 for (;;) { int available = getState(); int remaining = available - acquires;......}。即所有线程都可以进入自旋,只要锁有剩余量都可以尝试获取锁,体现为一种共享的控制。
3.5 Semaphore
public class Semaphore implements java.io.Serializable {
private static final long serialVersionUID = -3222578661600680210L;
/** All mechanics via AbstractQueuedSynchronizer subclass */
// 同步队列
private final Sync sync;
// 构造方法初始话锁数量
// 默认采用非公平策略
public Semaphore(int permits) {
sync = new NonfairSync(permits);
}
// 构造方法,带一个布尔参数,true表示采用公平策略,false表示采用非公平策略
public Semaphore(int permits, boolean fair) {
sync = fair ? new FairSync(permits) : new NonfairSync(permits);
}
}
3.5.1 acquire() 方法解析
// Semaphore
public void acquire() throws InterruptedException {
// 调用sync的acquireSharedInterruptibly,即响应中断的获取
// 因为sync继承AbstractQueuedSynchronizer
// 即调用AQS的acquireSharedInterruptibly
sync.acquireSharedInterruptibly(1);
}
// 进入AQS
public final void acquireSharedInterruptibly(int arg)
throws InterruptedException {
// 如果线程被中断,则响应中断
if (Thread.interrupted())
throw new InterruptedException();
// 否则调用tryAcquireShared,如果获取的锁小于0即获取锁失败则调用doAcquireSharedInterruptibly方法,进入同步队列排队
// 如果获取锁成功则不排队,走业务逻辑
if (tryAcquireShared(arg) < 0)
doAcquireSharedInterruptibly(arg);
}
// Semaphore 中tryAcquireShared的实现
// 公平策略
protected int tryAcquireShared(int acquires) {
for (;;) {
if (hasQueuedPredecessors())
return -1;
int available = getState();
int remaining = available - acquires;
if (remaining < 0 ||
compareAndSetState(available, remaining))
return remaining;
}
}
// 非公平策略
protected int tryAcquireShared(int acquires) {
return nonfairTryAcquireShared(acquires);
}
// 前面已经解析过,不在赘述
3.5.2 release() 方法解析
// Semaphore
public void release() {
sync.releaseShared(1);
}
// 进入AQS
public final boolean releaseShared(int arg) {
// 尝试释放锁, 如果释放锁成功
if (tryReleaseShared(arg)) {
// 线程出同步队列,返回true
doReleaseShared();
return true;
}
// 否则返回false
return false;
}
// Semaphore 中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");
if (compareAndSetState(current, next))
return true;
}
}
3.5.3 其他方法
方法 | 说明 | 调用 |
acquire(int permits) | 获取信号量,指定获取许可的个数,响应中断 | sync.acquireSharedInterruptibly(permits) |
acquireUninterruptibly() | 获取信号量,默认获取1个许可,不响应中断 | sync.acquireShared(1) |
acquireUninterruptibly(int permits) | 获取信号量,指定获取许可的个数,不响应中断 | sync.acquireShared(permits) |
release(int permits) | 释放信号量,指定释放许可的个数 | sync.releaseShared(permits); |
tryAcquire() | 尝试获取许可,如果获取成功返回true,否则返回false,不会阻塞线程,而且不响应中断 | sync.nonfairTryAcquireShared(1) |
tryAcquire(int permits) | 同上,可以指定获取许可的个数 | sync.nonfairTryAcquireShared(permits) |
tryAcquire(long timeout, TimeUnit unit) | 共享式超时获取 | sync.tryAcquireSharedNanos(1, unit.toNanos(timeout)) |
tryAcquire(int permits, long timeout, TimeUnit unit) | 同上,可以指定获取许可的个数 | sync.tryAcquireSharedNanos(permits, unit.toNanos(timeout)) |
availablePermits() | 获取可用许可数 | sync.getPermits() |
drainPermits() | 将剩下的信号量一次性消耗光,并且返回所消耗的信号量 | sync.drainPermits() |
reducePermits(int reduction) | 减少信号量的总数,不会导致任何线程阻塞,调用该方法可能会导致信号量最终为负数 | sync.reducePermits(reduction) |
isFair() | 是否采用公平策略 | |
hasQueuedThreads() | 是否是已排队的线程 | |
getQueueLength() | 获取排队线程的长度 | |
getQueuedThreads() | 获取排队线程 |
4. 总结
Semaphore是一个有效的流量控制工具,它基于AQS共享锁实现。我们常常用它来控制对有限资源的访问。使用步骤
- 每次使用资源前,先申请一个信号量,如果资源数不够,就会阻塞等待;
- 每次释放资源后,就释放一个信号量。
此篇文章没有更深的解析AQS和CAS原理,想深挖的同学可以去看这两篇文章解析AQS实现原理_我不是欧拉_的博客-CSDN博客