1.概述
Semphore(信号量)是一种线程同步工具,它主要用于多个线程对同一个资源进行并行操作,合理并发占用有限的公共资源。Semaphore可以理解为管理一组数量固定许可证的机构,当有大量线程来请求资源时,Semaphore会给一部分线程许可证,拿到许可证的线程才能访问资源,访问结束后归还许可证给Semaphore,Semaphore继续将证书发给排队的一个个线程;使用Semaphore可以控制并发资源的线程个数。本文将通过案例对Semaphore进行演示,并通过源码进行分析,从而全面展示Semaphore的原理。
2.案例分析
首先分析一个场景:假如一个停车场有5个停车位,此时有10辆车都需要进行停车服务。正常的方案是应该给这10辆车编号,先让1-5号停车,剩下的5辆排队;然后等停车场每离开一辆车,再把这个车位分配给剩余5辆中的某辆。
根据案例编写的Demo如下:
public class Parking {
static Semaphore semaphore = new Semaphore(5);
public static void main(String[] args) {
for (int i = 1; i <= 10; i++) {
final int temp = i;
new Thread(() -> {
try {
semaphore.acquire();
log.info("车辆:" + temp + ",停车成功!");
TimeUnit.SECONDS.sleep(1);
} catch (InterruptedException e) {
log.error("线程异常:{}", e);
} finally {
log.info("线程:" + Thread.currentThread().getName() + "离开,释放车位:" + temp);
semaphore.release();
}
}, "Thread" + i).start();
}
}
}
运行结果如下图所示:
在上述代码中,我们首先初始化Semaphore的容量为5,即停车场的可停车辆为5,每次停车时,都会调用semaphore.acquire()方法来占用一个车位,车辆离开时,调用semaphore.release()来进行车位的释放。
3.源码分析
3.1 Sync
semaphore的类图如下图所示,它包含三个内部类:Sync、FailSync、NonfairSync。Sync内部类是继承于AQS(AbstractQueuedSynchronizer),并做了具体的实现。
Sync类的代码如下图所示:
abstract static class Sync extends AbstractQueuedSynchronizer {
private static final long serialVersionUID = 1192457210091910933L;
//构造方法,指定需要锁的数量
Sync(int permits) {
setState(permits);
}
//调用AQS中getState()方法获取可用锁数量
final int getPermits() {
return getState();
}
//非公平方式加锁,直接开启线程加锁
final int nonfairTryAcquireShared(int acquires) {
for (;;) {
//获取当前剩余锁数量
int available = getState();
//计算获取锁后,剩余锁数量(可用锁-请求锁数量)
int remaining = available - acquires;
//CAS加锁操作,失败则自旋
if (remaining < 0 ||
compareAndSetState(available, remaining))
//大于0表示加锁成功,小于0表示失败
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");
//调用AQS中compareAndSetState方法进行加锁,成功返回true,失败则自旋
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))
//调用AQS中compareAndSetState方法进行加锁,失败则自旋
return;
}
}
//一次性获取剩余锁数量并全部加锁
final int drainPermits() {
for (;;) {
//获取当前剩余锁数量
int current = getState();
//获取当前线程中所有锁并加锁
if (current == 0 || compareAndSetState(current, 0))
return current;
}
}
}
3.2 FailSync(公平性)
static final class FairSync extends Sync {
private static final long serialVersionUID = 2014338818796000944L;
//构造方法,指定需要锁的数量
FairSync(int permits) {
super(permits);
}
protected int tryAcquireShared(int acquires) {
//死循环
for (;;) {
//判断AQS的CLH队列中是否有等待队列,若有,则直接返回-1,表示加锁失败
if (hasQueuedPredecessors())
return -1;
int available = getState();
int remaining = available - acquires;
if (remaining < 0 ||
compareAndSetState(available, remaining))
return remaining;
}
}
}
3.3 NonfairSync(非公平性)
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);
}
}
这里解释一下公平性和非公平性,其实和ReentrantLock一样,Semaphore也存在公平和非公平性两种情况。不公平意味着它不会去保证线程获取许可的顺序,Semaphore 会在线程等待之前为调用 acquire 的线程分配一个许可,拥有这个许可的线程会自动将自己置于线程等待队列的头部。当这个参数为 true 时,Semaphore 确保任何调用 acquire 的方法,都会按照先入先出的顺序来获取许可。由下面构造方式可知,Semaphore默认是非公平锁,可以根据传入的fair值来选择公平锁。
public Semaphore(int permits) {
sync = new NonfairSync(permits);
}
public Semaphore(int permits, boolean fair) {
sync = fair ? new FairSync(permits) : new NonfairSync(permits);
}
从上述NonfairSync和FailSync的源码可以看出,他们最大的区别在于tryAcquireShared。NonfairSync不管当前线程前是否有线程在排队,而是直接判断信号许可量和CAS方法是否可行。FairSync会先去判断队列中是否有线程,如果有的话则直接获取失败。
3.2 核心方法解析
方法名称 | 源码 | 解释 |
---|---|---|
acquire() | public void acquire() throws InterruptedException { sync.acquireSharedInterruptibly(1);} | 尝试获取一个许可,如果获取失败,则自旋等待,直到其它线程释放许可或该线程被中断 |
acquire(int permits) | public void acquire(int permits) throws InterruptedException { if (permits < 0) throw new IllegalArgumentException();sync.acquireSharedInterruptibly(permits);} | 尝试获取指定数量许可,如果获取失败,则自旋等待,直到其它线程释放许可或该线程被中断 |
acquireUninterruptibly() | public void acquireUninterruptibly() { sync.acquireShared(1);} | 尝试获取一个许可,方法作用是在等待许可的情况下不允许被中断,如果成功获得锁,则取得许可成功 |
acquireUninterruptibly(int permits) | public void acquireUninterruptibly(int permits) { if (permits < 0) throw new IllegalArgumentException();sync.acquireShared(permits); } | 尝试获取指定数量许可,方法作用是在等待许可的情况下不允许被中断,如果成功获得锁,则取得指定的permits许可个数 |
tryAcquire() | public boolean tryAcquire() {return sync.nonfairTryAcquireShared(1) >= 0;} | 尝试获得一个许可,如果获得许可成功返回true,如果失败则返回fasle,它不会等待,立即返回 |
tryAcquire(int permits) | public boolean tryAcquire(int permits) {if (permits < 0) throw new IllegalArgumentException();return sync.nonfairTryAcquireShared(permits) >= 0;} | 尝试获得指定数量许可,如果获得许可成功返回true,如果失败则返回fasle,它不会等待,立即返回 |
tryAcquire(long timeout, TimeUnit unit) | public boolean tryAcquire(long timeout, TimeUnit unit) throws InterruptedException { return sync.tryAcquireSharedNanos(1, unit.toNanos(timeout));} | 尝试在指定时间内获得一个许可,如果在指定时间内没有获得许可则则返回false,反之返回true |
tryAcquire(int permits, long timeout, TimeUnit unit) | public boolean tryAcquire(int permits, long timeout, TimeUnit unit)throws InterruptedException {if (permits < 0) throw new IllegalArgumentException();return sync.tryAcquireSharedNanos(permits, unit.toNanos(timeout));} | 尝试在指定时间内获得指定数量许可,如果在指定时间内没有获得许可则则返回false,反之返回true |
release() | public void release() { sync.releaseShared(1);} | 用于在线程访问资源结束后,释放一个许可,以使其他等待许可的线程可以进行资源访问 |
release(int permits) | public void release(int permits) {if (permits < 0) throw new IllegalArgumentException();sync.releaseShared(permits);} | 用于在线程访问资源结束后,释放指定数量许可,以使其他等待许可的线程可以进行资源访问 |
availablePermits() | public int availablePermits() {return sync.getPermits();} | 查看当前可用许可数 |
drainPermits() | public int drainPermits() {return sync.drainPermits();} | 获取当前所有可用的许可 |
reducePermits(int reduction) | protected void reducePermits(int reduction) {if (reduction < 0) throw new IllegalArgumentException();sync.reducePermits(reduction);} | 尝试获取一个许可,如果获取失败,则自旋等待,直到其它线程释放许可或该线程被中断 |
isFair() | public boolean isFair() {return sync instanceof FairSync;} | 判断是否是公平锁 |
hasQueuedThreads() | public final boolean hasQueuedThreads() {return sync.hasQueuedThreads();} | 查看当前队列中是否有线程等待 |
getQueueLength() | public final int getQueueLength() {return sync.getQueueLength();} | 获取当前等待线程队列的长度 |
getQueuedThreads() | protected Collection< Thread> getQueuedThreads() {return sync.getQueuedThreads(); } | 获取当前队列中线程列表 |
toString() | public String toString() {return super.toString() + "[Permits = " + sync.getPermits() + “]”;} | toString方法 |
4.小结
1.Semaphore与CountDownLatch、CyclicBarrier类似,都可以控制线程访问数量;
2.Semphore底层主要还是基于AQS的,方法入口均是Sync类,默认使用非公平锁;
3.Semphore的核心在于,通过CAS在加锁时判断是否有足够的锁数量,在释放锁更新锁的数量。
5.参考文献
1.https://juejin.cn/post/6844904083216662536
2.https://juejin.cn/post/6950219938929836063
3.https://juejin.cn/post/6844903537508368398
4.https://juejin.cn/post/6951588688262332430
5.https://www.bilibili.com/video/BV1mQ4y1Z795
6.附录
附录展示了Semaphore的内部方法图: