目录
Semaphore,信号量,可以用来控制同时访问特定资源的线程数量,可以将信号量看做是在向外分发使用资源的许可证,只有成功获取许可证,才能使用资源。
底层基于AQS实现。存在Sync、NonfairSync、FairSync三个内部类,Sync类继承自AbstractQueuedSynchronizer抽象类,NonfairSync与FairSync类继承自Sync类。
两种模式下分别提供了限时/不限时、响应中断/不响应中断的获取资源的方法(限时获取总是及时响应中断的),而所有的释放资源的 release() 操作是统一的。
主要变量和方法
acquire() 获取一个令牌,在获取到令牌、或者被其他线程调用中断之前线程一直处于阻塞状态。
acquire(int permits) 获取多个令牌,在获取到令牌、或者被其他线程调用中断、或超时之前线程一直处于阻塞状态。
acquireUninterruptibly() 获取一个令牌,在获取到令牌之前线程一直处于阻塞状态(忽略中断)。
tryAcquire() 尝试获得令牌,返回获取令牌成功或失败,不阻塞线程。
tryAcquire(long timeout, TimeUnit unit) 尝试获得令牌,在超时时间内循环尝试获取,直到尝试获取成功或超时返回,不阻塞线程。
release() 释放一个令牌,唤醒一个获取令牌不成功的阻塞线程。
hasQueuedThreads() 等待队列里是否还存在等待线程。
getQueueLength() 获取等待队列里阻塞的线程数。
drainPermits() 清空令牌把可用令牌数置为0,返回清空令牌的数量。
availablePermits() 返回可用的令牌数量。
内部类同步器Sync
abstract static class Sync extends AbstractQueuedSynchronizer {
private static final long serialVersionUID = 1192457210091910933L;
// 赋值setState为总许可数
Sync(int permits) {
setState(permits);
}
// 剩余许可数
final int getPermits() {
return getState();
}
// 自旋 + CAS 非公平获取
final int nonfairTryAcquireShared(int acquires) {
for (;;) {
// 剩余可用许可数
int available = getState();
// 本次获取许可后,剩余许可
int remaining = available - acquires;
// 如果获取后,剩余许可大于0,则CAS更新剩余许可,否则获取更新失败
if (remaining < 0 ||
compareAndSetState(available, remaining))
return remaining;
}
}
// 自旋 + CAS 释放许可
// 由于未对释放许可数做限制,所以可以通过release动态增加许可数量
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;
}
}
// 自旋 + CAS 减少许可数量
final void reducePermits(int reductions) {
for (;;) {
// 当前剩余许可
int current = getState();
// 更新值
int next = current - reductions;
// 如果更新值比当前剩余许可大,抛出益处
if (next > current) // underflow
throw new Error("Permit count underflow");
// CAS 更新许可数
if (compareAndSetState(current, next))
return;
}
}
// 丢弃所有许可
final int drainPermits() {
for (;;) {
int current = getState();
if (current == 0 || compareAndSetState(current, 0))
return current;
}
}
}
非公平NonfairSync
重写了AQS的tryAcquireShared方法,调用父类Sync的nonfairTryAcquireShared方法,表示按照非公平策略进行资源的获取。
/**
* 非公平模式
*/
static final class NonfairSync extends Sync {
private static final long serialVersionUID = -2694183684443567898L;
NonfairSync(int permits) {
super(permits);
}
protected int tryAcquireShared(int acquires) {
return nonfairTryAcquireShared(acquires);
}
}
公平FairSync
重写了AQS的tryAcquireShared方法
/**
* 公平模式
*/
static final class FairSync extends Sync {
private static final long serialVersionUID = 2014338818796000944L;
FairSync(int permits) {
super(permits);
}
// 公平模式获取许可
// 公平模式不论许可是否充足,都会判断同步队列中是否线程在等待,如果有,获取失败,排队阻塞
protected int tryAcquireShared(int acquires) {
for (;;) {
// 如果有线程在排队,立即返回
if (hasQueuedPredecessors())
return -1;
// 自旋 + CAS获取许可
int available = getState();
int remaining = available - acquires;
if (remaining < 0 ||
compareAndSetState(available, remaining))
return remaining;
}
}
}
代码执行过程(以acquire()为例)
- 创建信号量对象
- 获取许可
- 释放许可
public class MyTest {
public static void main(String[] args) {
ExecutorService executorService = Executors.newCachedThreadPool();
//1、信号量,只允许5个线程同时访问
Semaphore semaphore = new Semaphore(5);
for (int i = 0; i < 100; i++) {
executorService.submit(() -> {
try {
//2、获取许可
semaphore.acquire();
Thread.sleep(1000);
//3、释放
semaphore.release();
} catch (InterruptedException e) {
e.printStackTrace();
}
});
}
executorService.shutdown();
}
}
//1、构造方法
public Semaphore(int permits) {
sync = new NonfairSync(permits);
}
//2、获取许可
public void acquire() throws InterruptedException {
sync.acquireSharedInterruptibly(1);
}
public final void acquireSharedInterruptibly(int arg)
throws InterruptedException {
if (Thread.interrupted())
throw new InterruptedException();
// 获取许可,剩余许可 >= 0,则获取许可成功 <0 获取许可失败,进入排队
if (tryAcquireShared(arg) < 0)
//调用AQS的doAcquireSharedInterruptibly方法入队
doAcquireSharedInterruptibly(arg);
}
//获取许可失败,当前线程进入同步队列,排队阻塞
private void doAcquireSharedInterruptibly(int arg)
throws InterruptedException {
// 创建同步队列节点,并入队列
final Node node = addWaiter(Node.SHARED);
boolean failed = true;
try {
for (;;) {
// 如果当前节点是第二个节点,尝试获取锁
final Node p = node.predecessor();
if (p == head) {
int 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);
}
}
//3、释放许可
public void release() {
sync.releaseShared(1); //调用AQS的releaseShared方法
}
public final boolean releaseShared(int arg) {
//调用Semaphore重写的tryReleaseShared方法CAS修改state
if (tryReleaseShared(arg)) {
//释放成功去队列唤醒线程
doReleaseShared();
return true;
}
return false;
}
//CAS修改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;
}
}
以上demo使用的是acquire()方法,如果获取许可失败会阻塞线程并入队列,如果希望失败直接返回,可以使用tryAcquire()
//CAS修改state,失败直接return false
public boolean tryAcquire() {
return sync.nonfairTryAcquireShared(1) >= 0;
}
tryAcquire()示例:
public static void main(String[] args) {
ExecutorService executorService = Executors.newCachedThreadPool();
//信号量,只允许5个线程同时访问
Semaphore semaphore = new Semaphore(5);
for (int i = 0; i < 100; i++) {
executorService.submit(() -> {
try {
//获取许可
if (!semaphore.tryAcquire()) {
return;
}
System.out.println(Thread.currentThread());
Thread.sleep(10000);
//释放
semaphore.release();
} catch (InterruptedException e) {
e.printStackTrace();
}
});
}
executorService.shutdown();
}
结果可能是只有5个线程执行成功
Thread[pool-1-thread-1,5,main]
Thread[pool-1-thread-2,5,main]
Thread[pool-1-thread-3,5,main]
Thread[pool-1-thread-4,5,main]
Thread[pool-1-thread-5,5,main]
以上demo是为了演示Semaphore的使用过程,没有考虑线程中断异常等情况,可能会导致许可多释放或少释放。
比如:release 操作如果放到 finally 代码块里面去执行,可能会在acquire()发生中断异常,并未获取到令牌,导致release多释放令牌。
release 操作如果放到acquire()之后执行,两者在同一个try catch块,可能会在acquire()后发生异常,导致少释放令牌。
一种可能可用的做法是:在acquire()上catch异常,catch后直接return。未cacth到异常证明成功获取到许可,在后面的代码逻辑上加try cacth finally,在finally中执行release。
如果大家有更好的方法欢迎留言。