阅读本文前,请先查看《java8 多线程并发之AQS详解》和《java8 ReentrantLock加解锁原理详解》,本文是在前面两篇文章基础上的延续。
Semaphore类表示信号量,初始时我们设置一个信号量初始值,该初始值可以为任意值(负数也可以,一般为正值),每个线程可以申请一定量的信号量,如果信号量不够,则阻塞线程,每个线程执行完,会将信号量释放,以供后续线程申请使用。Semaphore允许多个线程同时持有一定量的信号量,因此Semaphore相当于共享锁,信号量初始值就相当于锁的个数,只要线程持有的锁个数不超过总的锁个数,就允许多个线程同时访问资源。
一、如何使用Semaphore
下面是一个使用Semaphore控制多线程访问资源的例子。
public class Main {
public static void main(String argv[]){
//初始信号量为10个
Semaphore s=new Semaphore(4);
AtomicInteger cnt=new AtomicInteger(0);
Runnable run=()->{
s.acquireUninterruptibly(2);
int no=cnt.getAndAdd(1);
System.out.println("线程"+no+"-开始运行时间为:"+(new Date()));
try {
Thread.sleep(3000);//模拟访问资源
}catch (Exception e){}
s.release(2);
System.out.println("线程"+no+"-结束运行时间为:"+(new Date()));
};
for(int i=0;i<6;i++){
Thread thread=new Thread(run);
thread.start();
}
}
}
运行结果为:
线程0-开始运行时间为:Sat Jan 30 16:38:23 CST 2021
线程1-开始运行时间为:Sat Jan 30 16:38:23 CST 2021
线程2-开始运行时间为:Sat Jan 30 16:38:26 CST 2021
线程1-结束运行时间为:Sat Jan 30 16:38:26 CST 2021
线程3-开始运行时间为:Sat Jan 30 16:38:26 CST 2021
线程0-结束运行时间为:Sat Jan 30 16:38:26 CST 2021
线程4-开始运行时间为:Sat Jan 30 16:38:29 CST 2021
线程2-结束运行时间为:Sat Jan 30 16:38:29 CST 2021
线程5-开始运行时间为:Sat Jan 30 16:38:29 CST 2021
线程3-结束运行时间为:Sat Jan 30 16:38:29 CST 2021
线程4-结束运行时间为:Sat Jan 30 16:38:32 CST 2021
线程5-结束运行时间为:Sat Jan 30 16:38:32 CST 2021
初始信号量为4个,每个线程申请两个,那么最多可以同时有两个线程运行。所以随着线程不断的申请信号量,当信号量个数小于等于0的时候,如果后续线程再来申请,那么会阻塞线程。
Semaphore的初始信号量并没有要求必须大于0,小于等于0也可以,但是此时必须首先有线程释放信号量,否则该信号量会阻塞所有的申请线程。
Semaphore也有公平信号量和非公平信号,其含义与ReentrantLock的公平锁和非公平锁一样,这里不再详细介绍。
二、源码解析
1、构造方法
Semaphore提供了两个构造方法:
public Semaphore(int permits) {
sync = new NonfairSync(permits);
}
public Semaphore(int permits, boolean fair) {
sync = fair ? new FairSync(permits) : new NonfairSync(permits);
}
第二个构造方法的fair用于设置公平信号量和非公平信号量。
从构造方法可以看到,NonfairSync表示非公平信号量,FairSync表示公平信号量。Semaphore中的方法基本上都是委托给这两个类处理的,比如:
public void acquireUninterruptibly() {
sync.acquireShared(1);
}
因此Semaphore的核心逻辑都在NonfairSync和FairSync中,下面重点介绍这两个类的实现逻辑。
2、Sync
Sync是NonfairSync和FairSync的共同基类,同时Sync继承自AQS。
abstract static class Sync extends AbstractQueuedSynchronizer {
Sync(int permits) {
//信号量的初始值直接设置给了AQS中的属性state
//因此属性state就表示了总的信号量
setState(permits);
}
//返回当前剩余信号量
final int getPermits() {
return getState();
}
//申请信号量,使用当前剩余信号量与申请的信号量比较,如果前者大,则修改剩余信号量,并将该值返回,
//如果后者大,则不做改变,将还需要增加的信号量个数返回。
//该方法在非公平信号量中使用
final int nonfairTryAcquireShared(int acquires) {
for (;;) {
int available = getState();
int remaining = available - acquires;
if (remaining < 0 ||
compareAndSetState(available, remaining))
return remaining;
}
}
//释放信号量
//信号量相当于AQS的共享模式,因此需要实现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;
}
}
//减少信号量个数,
//该方法由Semaphore.reducePermits(int reductions)方法调用,入参reductions必须大于0,
//该方法可以同于跟踪不可用资源,当资源减少,信号量个数也需要同步减少,那么便可以调用该方法减少信号量
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;
}
}
//返回剩余信号量,同时将剩余信号量修改为0
final int drainPermits() {
for (;;) {
int current = getState();
if (current == 0 || compareAndSetState(current, 0))
return current;
}
}
}
3、FairSync
FairSync表示公平信号量。
//代码有删减
static final class FairSync extends Sync {
FairSync(int permits) {
super(permits);
}
//信号量相当于AQS的共享模式,因此FairSync需要实现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;
}
}
}
FairSync实现了AQS共享模式下的tryAcquireShared()方法,每次调用Semaphore中申请信号量的方法(acquire()/acquireUninterruptibly()等)都会调用tryAcquireShared()方法,该方法首先检查队列中是否有等待信号量的线程,如果有,AQS会将当前线程放到队列中,并阻塞线程,等待后面重新调度;如果没有则直接尝试修改属性state,修改成功则表示信号量申请成功。
在公平信号量中,FairSync严格按照先进先出的原则申请信号量。
4、NonfairSync
NonfairSync表示非公平信号量。
//代码有删减
static final class NonfairSync extends Sync {
NonfairSync(int permits) {
super(permits);
}
protected int tryAcquireShared(int acquires) {
return nonfairTryAcquireShared(acquires);
}
}
NonfairSync的tryAcquireShared()方法直接调用Sync的nonfairTryAcquireShared()方法,nonfairTryAcquireShared()方法在前面已经介绍过了,与FairSync相比,非公平信号量不关心队列中是否有等待的线程,而是直接尝试申请,只有申请失败了才会进入队列中,之后在队列里面按照先进先出的原则等待再次申请信号量。