本篇文章的主要内容:
1:举例说明Semaphore
2:Semaphore的原理剖析
3:Semaphore的源码剖析
1:举例说明Semaphorepublic class SemaphoreTest {
private static Semaphore sh = new Semaphore(3);
public static void main(String[] args) {
for (int i = 0; i < 10; i++) {
int finalI = i;
new Thread(() -> {
try {
sh.acquire();
String dateStr = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss").format(new Date());
System.out.println("Thread-" + (finalI + 1) + "获取运行权限:" + dateStr);
Thread.sleep(1000);
sh.release();
} catch (Exception e) {
e.printStackTrace();
}
}).start();
}
}
}
运行结果如下图:
通过上面的运行结果,会发现一个规律,那就是一次最多有三个线程同时运行,在看上面的demo,我们在创建Semaphore的时候传递了参数3,这两者似乎存在着联系,我们可以大胆的猜想。
创建Semaphore对象传递的参数决定了多少个线程能够同时获取资源,然后执行代码逻辑。
我们对Java并发包中的AQS框架非常的熟悉了,这个类的原理似乎和AQS中共享资源的获取和释放非常的类似,我们首先回顾一下这个知识点。
1:state<0:表示获取资源失败
2:state==0:表示获取资源成功,但是没有剩余的资源了
3:state>0:表示获取资源成功,并且还有剩余的资源
在创建Semaphore时传递的参数就是state,我们传递3,说明同时能够有3个线程获取到资源,其他线程需要等待,直到其他线程释放资源。
2:Semaphore的原理剖析
通过上面的demo,大家是否对Semaphore的用法有所了解了,它的内部原理其实就是AQS中的共享模式下对资源的获取和释放。
1:创建Semaphore时传递的参数,底层赋值给了AQS的共享资源state
2:Semaphore中的acquire()方法其实就是尝试获取资源,如果还有剩余的资源,则代表获取了锁,就可以继续执行了,如果没有剩余的资源,那么这个线程就会放入CLH等待队列,等待有资源。
3:Semaphore中的release()方法就是释放资源。
通过上面的介绍,对Semaphore总结一句话:同一个时间允许多少个线程获取到共享资源,获取到资源的线程能够继续执行代码逻辑,没有获取到资源的线程则需要等待了,直到有共享资源或者被其他线程中断。
结合上面的例子,开始进行详细的分析,可能上面的demo执行结果有所不同,因为多线程下,哪个线程获取到CPU,哪个线程首先执行,但是相同的就是同一时间最多有3个线程同时执行。
1:Thread2首先获取到了CPU的执行权,此时共享资源是3,所以Thread2获取到了资源,并且将资源数量-1,此时共享资源是2
2:Thread3获取到了CPU的执行权,此时共享资源是2,所以Thread3获取到了资源,并且将资源数量-1,此时共享资源是1
3:Thread1获取到了CPU的执行权,此时共享资源是1,所以Thread1获取到了资源,并且将资源数量-1,此时共享资源0.
4:Thread5获取到了CPU的执行权,此时共享资源是0,所以Thread5获取资源失败,那么Thread5就是封装成Node节点存放到CLH等待队列中。
5:下面的线程以此类推。
上面的步骤仅仅是一种情况,可能Thread5获取CPU后,Thread2已经释放了资源,但是不管怎样,因为只有3个共享资源,所以同时只能有3个线程获取。
接下来我们从源码角度来看看Semaphore是否像我们上面所说的。
3:Semaphore的源码剖析
通过前几篇文章的学习,我们知道,如果利用AQS框架来实现并发功能,那一定有一个内部类,并且这个内部类继承了AQS,我们搜索Semaphore源码。
上图中的Sync就是AQS的子类,其中NonfairSync和FairSync是Sync的两个子类,分别对应的是非公平模式和公平模式。
3.1:Semaphore的构造函数
public Semaphore(int permits) {
sync = new NonfairSync(permits);
}
public Semaphore(int permits, boolean fair) {
sync = fair ? new FairSync(permits) : new NonfairSync(permits);
}
第一个参数:permits:表示初始化的共享资源
第二个参数:fair:true:表示公平模式,false:表示非公平模式,默认是非公平模式。
这两个构造函数底层要么调用非公平模式(NonfairSync),要么调用公平模式(FairSync),然后把permits传递进去,那么我们进入NonfairSync
NonfairSync(int permits) {
super(permits);
}
Sync(int permits) {
setState(permits);
}
从上面的代码可以看到Semaphore构造函数最终调用的是Sync的构造函数,而Sync直接调用AQS的setState()方法,将permits赋值给了AQS的共享资源state。
3.2:acquire()方法
public void acquire() throws InterruptedException {
sync.acquireSharedInterruptibly(1);
}
public final void acquireSharedInterruptibly(int arg)
throws InterruptedException {
if (Thread.interrupted())
throw new InterruptedException();
if (tryAcquireShared(arg) < 0)
doAcquireSharedInterruptibly(arg);
}
上面的acquire()方法底层直接调用AQS中的acquireSharedInterruptibly()方法,而这个方法是AQS中共享模式下获取资源的模板方法,而AQS的子类必须实现tryAcquireShared()方法,那么我们接下来到Sync中看看这个方法怎样实现的。以非公平类为例:NonfairSync
final int nonfairTryAcquireShared(int acquires) {
for (;;) {
int available = getState();
int remaining = available - acquires;
if (remaining < 0 ||
compareAndSetState(available, remaining))
return remaining;
}
}
首先是一个for无限循环,然后通过调用AQS的getState()方法获取到可用的共享资源state,然后减去这一次所需要的共享资源,获取到剩余的remaining,然后有两个结果
第一个结果:如果剩余资源remaining<0:说明剩余的资源不足,获取资源失败
第二个结果,如果剩余资源remaining>=0:说明剩余的资源能够满足,获取资源成功,只是当remaining==0时没有剩余的资源了,remaining>0时还有剩余的资源。
3.3:release()方法
public void release() {
sync.releaseShared(1);
}
public final boolean releaseShared(int arg) {
if (tryReleaseShared(arg)) {
doReleaseShared();
return true;
}
return false;
}
release()方法底层直接调用了AQS的releaseShared()方法进行释放资源,而这个方法也是一个模板方法,AQS的子类只需要实现tryReleaseShared()即可,那么我们接下来看看Sync是怎样实现这个方法的。
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;
}
}
这个方法首先调用AQS的getState()方法获取到共享资源state,然后联合此次释放的资源releases,通过CAS机制更新state,直到更新成功才返回。