文章目录
前言
Semaphore(信号量),是用来控制同时访问特定资源的线程数量,它通过协调各个线程,以保证合理的使用公共资源。
Semaphore源码分析(基于JDK1.8)
一、Semaphore整体结构
二、内部类Sync
Semaphore内部实现了Sync抽象类,而且还有公平锁和非公平锁区分,这和ReentrantLock十分相似。我们来看一下。
abstract static class Sync extends AbstractQueuedSynchronizer {
private static final long serialVersionUID = 1192457210091910933L;
/*设置Sync的初始状态*/
Sync(int permits) {
setState(permits);
}
/*获取Sync当前的状态*/
final int getPermits() {
return getState();
}
/*
* 非公平锁尝试获取资源
* 我们可以看到Sync内部使用的锁结构是共享锁
*/
final int nonfairTryAcquireShared(int acquires) {
for (;;) {
/*获取锁的状态*/
int available = getState();
int remaining = available - acquires;
/*
1.如果state小于0,表示没有资源,直接返回
2.如果cas设置成功,表示获取资源成功,直接返回;若cas失败,会自旋直至设置成功或者没有资源
*/
if (remaining < 0 ||
compareAndSetState(available, remaining))
return remaining;
}
}
/*释放共享锁资源*/
protected final boolean tryReleaseShared(int releases) {
for (;;) {
/*获取锁的状态*/
int current = getState();
int next = current + releases;
/*permit的最大值是Integer.MAX_VALUE,如果超过该上限的话,说明到达了最大许可数,直接抛出异常*/
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");
/*通过cas更新锁的状态值*/
if (compareAndSetState(current, next))
return;
}
}
/*获取剩余全部的许可*/
final int drainPermits() {
for (;;) {
int current = getState();
/*cas将当前状态设置为0*/
if (current == 0 || compareAndSetState(current, 0))
return current;
}
}
}
Sync内部类结构很简单,函数也很容易理解,它继承了AQS,主要实现了非公平锁获取锁的函数以及释放锁的函数。由于这些函数基本没有比较难以理解的点,这里我们就不在赘述。不过还有一点值得注意的是,ReentrantLock内部Sync的实现是利用AQS的独占锁,而Semaphore内部Sync的实现是利用AQS的共享锁。
2.1 NofairSync
非公平锁的实现:
static final class NonfairSync extends Sync {
private static final long serialVersionUID = -2694183684443567898L;
NonfairSync(int permits) {
super(permits);
}
/*调用Sync类的nonfairTryAcquireShared方法*/
protected int tryAcquireShared(int acquires) {
return nonfairTryAcquireShared(acquires);
}
}
2.2 FairSync
公平锁的实现:
static final class FairSync extends Sync {
private static final long serialVersionUID = 2014338818796000944L;
FairSync(int permits) {
super(permits);
}
protected int tryAcquireShared(int acquires) {
for (;;) {
/*判断CLH同步队列中是否有正在等待获取锁的线程 如果有,直接返回-1*/
if (hasQueuedPredecessors())
return -1;
/*如果CLH队列中没有等待获取的线程,那么执行下面的步骤,基本和非公平锁的不走差不多*/
int available = getState();
int remaining = available - acquires;
if (remaining < 0 ||
compareAndSetState(available, remaining))
return remaining;
}
}
}
2.3 公平锁和非公平锁的差异
我们可以看到,Semaphore非公平锁和公平锁就多了一步判断,这步判断就是利用hasQueuedPredecessors()
判断CLH同步队列中是否有正在等到获取锁的线程,如果有直接返回-1,如果没有,那么当前线程就可以尝试去获取资源。
其实这里和ReentrantLock是十分相似的,至于hasQueuedPredecessors()
的分析,可以查看JDK源码系列 ReentrantLock 公平锁和非公平锁的实现原理,这里就不再赘述。
讲完Semaphore的锁的实现,我们来看一下Semaphore的主要方法。
三、Semaphore主要方法
3.1 构造方法
/*该构造方法默认使用的是非公平锁,创建一个Semaphore实例并设置许可的数量*/
public Semaphore(int permits) {
sync = new Semaphore.NonfairSync(permits);
}
/*该构造方法可以指定Semaphore使用的锁类型*/
public Semaphore(int permits, boolean fair) {
sync = fair ? new Semaphore.FairSync(permits) : new Semaphore.NonfairSync(permits);
}
3.2 acquire
当前函数可以从Semaphore获取许可(默认获取一个许可),如果获取许可成功,将继续执行,若获取许可失败,当前线程将被park进入waiting状态。
public void acquire() throws InterruptedException {
sync.acquireSharedInterruptibly(1);
}
我们可以看到acquire
其实调用的是AQS的acquireSharedInterruptibly((int arg)
方法,该方法和acquireShared((int arg)
类似,只是该方法对中断是敏感的,会响应中断。
具体可以看JDK源码系列 AQS续篇共享锁源码实现分析中关于共享锁acquireShared((int arg)
方法的解析,我们这里不再赘述。
当线程获取到许可时,即获取到了共享锁,那么该线程可以继续往下执行。若线程没有获取到许可,那么该线程会以共享结点的形式加入到CLH同步队列中,等待获取锁。
而acquire(int permits)
是从Semaphore中获取指定数量的许可。
至于acquireUninterruptibly()
和acquireUninterruptibly(int permits)
这两个方法和上面讲的相似,只是这两个方法不会抛出中断异常,它们会将中断(中断标志为true)外抛,由我们自己决定如何去处理中断。
3.3 tryAcquire
调用该方法可以尝试去获取许可。
public boolean tryAcquire() {
/*调用非公平锁的尝试获取许可(默认1个)的方法*/
return sync.nonfairTryAcquireShared(1) >= 0;
}
/*尝试在指定时长内去获取许可*/
public boolean tryAcquire(long timeout, TimeUnit unit)
throws InterruptedException {
return sync.tryAcquireSharedNanos(1, unit.toNanos(timeout));
}
//还有两个tryAcquire是获取指定许可,这里就不列出来了
tryAcquire可以让当前线程尝试去获取许可。
tryAcquireSharedNanos
方法是在AQS的方法,作用是在指定时长内去尝试获取锁,这里不多说。
3.4 release
调用该方法可以释放许可(释放共享锁)并唤醒CLH队列中的线程来尝试获取许可(共享锁)。
public void release() {
/*调用AQS的releaseShared去释放资源*/
sync.releaseShared(1);
}
/*释放指定许可*/
public void release(int permits) {
if (permits < 0) throw new IllegalArgumentException();
sync.releaseShared(permits);
}
release方法很简单,就是释放当前线程所持有许可(共享锁),并唤醒CLH队列中的线程去尝试获取锁资源。
3.4 其余方法
/*当前可用的许可数*/
public int availablePermits() {
return sync.getPermits();
}
/*获取当前Semaphore剩余全部的许可数量*/
public int drainPermits() {
return sync.drainPermits();
}
/*减少许可的数量*/
protected void reducePermits(int reduction) {
if (reduction < 0) throw new IllegalArgumentException();
sync.reducePermits(reduction);
}
/*判断CLH队列是否还有线程在等待获取锁*/
public final boolean hasQueuedThreads() {
return sync.hasQueuedThreads();
}
/*获取CLH队列的结点的数量*/
public final int getQueueLength() {
return sync.getQueueLength();
}
以上,便是Semaphore的全部源码分析,我们可以看到,Semaphore其实是利用AQS的共享锁模式来控制同时访问特定资源的线程数量。
接下来我们来看一下相关应用:
public class SemaphoreTest {
private static final int THREAD_NUM = 30;
private static ThreadPoolExecutor threadPool = (ThreadPoolExecutor) Executors.newFixedThreadPool(THREAD_NUM);
private static Semaphore semaphore = new Semaphore(10);
static {
threadPool.prestartAllCoreThreads();
}
public static void main(String[] args) {
try {
for (int i = 0; i < THREAD_NUM; i++) {
threadPool.execute(()->{
try {
semaphore.acquire();
System.out.println("running");
Thread.sleep(1000);
semaphore.release();
} catch (InterruptedException e) {
e.printStackTrace();
}
});
}
}finally {
threadPool.shutdown();
}
}
}
在以上demo中,我们设置了Semaphore的许可数量为10。而在线程池有30个线程在运行,但是每次只允许10个线程并发执行。当当前线程调用acquire
获取许可成功并执行完毕,随后调用release
来释放许可,唤醒在CLH队列中的线程来获取许可(基本上是每次10个线程在并发运行)。
3.5 应用场景
从上面的例子我们已经明白了Semaphore的运行机制,通过该运行机制我们可以利用Semaphore来做流量控制,特别是公共资源有限的应用场景,比如数据库连接。假如有一个需求,要读取几万个文件的数据,因为都是IO 密集型任务 ,我们可以启动几十个线程并发地读取,但是如果读到内存后,还需要存储到数据库中,而数据库的连接数只有10个, 这时我们必须控制只有10个线程同时获取数据库连接保存数据,否则会报错无法获取数据库连接。这个时候,就可以使用 Semaphore 来做流量控制。 --[摘自Java并发编程的艺术]
至此,关于Semaphore的全部内容已经分析完毕。