JUC并发工具
一、CountDownLatch应用&源码分析
1.1 CountDownLatch介绍
CountDownLatch
就是JUC包下的一个工具,整个工具最核心的功能就是计数器
如果有三个业务需要并行处理,并且需要知道三个业务全部都处理完毕了
需要一个并发安全的计数器来操作
CountDownLatch
就可以实现
给CountDownLatch
设置一个数值。可以设置3
每个业务处理完毕之后,执行一次countDown
方法,指定的3每次在执行countDown
方法时,对3
进行-1
主线程可以在业务处理时,执行await
,主线程会阻塞等待任务处理完毕
当设置的3
基于countDown
方法减为0
之后,主线程就会被唤醒,继续处理后续业务
当咱们的业务中,出现2个以上允许并行处理的任务,并且需要在任务都处理完毕后,再做其他处理时,可以采用CountDownLatch
去实现这个功能
1.2 CountDownLatch应用
模拟有三个任务需要并行处理,在三个任务全部处理完毕后,再执行后续操作
CountDownLatch
中,执行countDown
方法,代表一个任务结束,对计数器-1
执行await
方法,代表等待计数器变为0
时,再继续执行
执行await(time,unit)
方法,代表等待time
时长,如果计数器不为0
,返回false
,如果在等待期间,计数器为0
,方法就返回true
一般CountDownLatch
更多的是基于业务去构建,不采用成员变量
static ThreadPoolExecutor executor = (ThreadPoolExecutor) Executors.newFixedThreadPool(3);
static CountDownLatch countDownLatch = new CountDownLatch(3);
public static void main(String[] args) throws InterruptedException {
System.out.println("主业务开始执行");
sleep(1000);
executor.execute(CompanyTest::a);
executor.execute(CompanyTest::b);
executor.execute(CompanyTest::c);
System.out.println("三个任务并行执行,主业务线程等待");
// 死等任务结束
// countDownLatch.await();
// 如果在规定时间内,任务没有结束,返回false
if (countDownLatch.await(10, TimeUnit.SECONDS)) {
System.out.println("三个任务处理完毕,主业务线程继续执行");
}else{
System.out.println("三个任务没有全部处理完毕,执行其他的操作");
}
}
private static void a() {
System.out.println("A任务开始");
sleep(1000);
System.out.println("A任务结束");
countDownLatch.countDown();
}
private static void b() {
System.out.println("B任务开始");
sleep(1500);
System.out.println("B任务结束");
countDownLatch.countDown();
}
private static void c() {
System.out.println("C任务开始");
sleep(2000);
System.out.println("C任务结束");
countDownLatch.countDown();
}
private static void sleep(long timeout){
try {
Thread.sleep(timeout);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
1.3 CountDownLatch源码分析
保证CountDownLatch
就是一个计数器,没有什么特殊的功能,查看源码也只是查看计数器实现的方式
发现CountDownLatch
的内部类Sync
继承了AQS,CountDownLatch
就是基于AQS实现的计数器
AQS就是一个state
属性,以及AQS双向链表
猜测计数器的数值实现就是基于state
去玩的
主线程阻塞的方式,也是阻塞在了AQS双向链表中
1.3.1 有参构造
就是构建内部类Sync
,并且给AQS中的state
赋值
// CountDownLatch的有参构造
public CountDownLatch(int count) {
// 健壮性校验
if (count < 0) throw new IllegalArgumentException("count < 0");
// 构建内部类,Sync传入count
this.sync = new Sync(count);
}
// AQS子类,Sync的有参构造
Sync(int count) {
// 就是给AQS中的state赋值
setState(count);
}
1.3.2 await方法
await
方法就时判断当前CountDownLatch
中的state
是否为0
,如果为0
,直接正常执行后续任务
如果不为0
,以共享锁的方式,插入到AQS的双向链表,并且挂起线程
// 一般主线程await的方法,阻塞主线程,等待state为0
public void await() throws InterruptedException {
sync.acquireSharedInterruptibly(1);
}
// 执行了AQS的acquireSharedInterruptibly方法
public final void acquireSharedInterruptibly(int arg) throws InterruptedException {
// 判断线程是否中断,如果中断标记位是true,直接抛出异常
if (Thread.interrupted())
throw new InterruptedException();
if (tryAcquireShared(arg) < 0)
// 共享锁挂起的操作
doAcquireSharedInterruptibly(arg);
}
// tryAcquireShared在CountDownLatch中的实现
protected int tryAcquireShared(int acquires) {
// 查看state是否为0,如果为0,返回1,不为0,返回-1
return (getState() == 0) ? 1 : -1;
}
private void doAcquireSharedInterruptibly(int arg) throws InterruptedException {
// 封装当前先成为Node,属性为共享锁
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);
}
}
1.3.3 countDown方法
countDown
方法本质就是对state - 1
,如果state - 1
后变为0
,需要去AQS的链表中唤醒挂起的节点
// countDown对计数器-1
public void countDown() {
// 是-1。
sync.releaseShared(1);
}
// AQS提供的功能
public final boolean releaseShared(int arg) {
// 对state - 1
if (tryReleaseShared(arg)) {
// state - 1后,变为0,执行doReleaseShared
doReleaseShared();
return true;
}
return false;
}
// CountDownLatch的tryReleaseShared实现
protected boolean tryReleaseShared(int releases) {
// 死循环是为了避免CAS并发问题
for (;;) {
// 获取state
int c = getState();
// state已经为0,直接返回false
if (c == 0)
return false;
// 对获取到的state - 1
int nextc = c-1;
// 基于CAS的方式,将值赋值给state
if (compareAndSetState(c, nextc))
// 赋值完,发现state为0了。此时可能会有线程在await方法处挂起,那边挂起,需要这边唤醒
return nextc == 0;
}
}
// 如何唤醒在await方法处挂起的线程
private void doReleaseShared() {
// 死循环
for (;;) {
// 拿到head
Node h = head;
// head不为null,有值,并且head != tail,代表至少2个节点
// 一个虚拟的head,加上一个实质性的Node
if (h != null && h != tail) {
// 说明AQS队列中有节点
int ws = h.waitStatus;
// 如果head节点的状态为 -1.
if (ws == Node.SIGNAL) {
// 先对head节点将状态从-1,修改为0,避免重复唤醒的情况
if (!compareAndSetWaitStatus(h, Node.SIGNAL, 0))
continue;
// 正常唤醒节点即可,先看head.next,能唤醒就唤醒,如果head.next有问题,从后往前找有效节点
unparkSuccessor(h);
}
// 会在Semaphore中谈到这个位置
else if (ws == 0 &&
!compareAndSetWaitStatus(h, 0, Node.PROPAGATE))
continue;
}
// 会在Semaphore中谈到这个位置
if (h == head)
break;
}
}
二、CyclicBarrier应用&源码分析
2.1 CyclicBarrier介绍
从名字上来看CyclicBarrier
,就是代表循环屏障
Barrier
屏障:让一个或多个线程达到一个屏障点,会被阻塞。屏障点会有一个数值,当达到一个线程阻塞在屏障点时,就会对屏障点的数值进行-1
操作,当屏障点数值减为0
时,屏障就会打开,唤醒所有阻塞在屏障点的线程。在释放屏障点之后,可以先执行一个任务,再让所有阻塞被唤醒的线程继续之后后续任务
Cyclic
循环:所有线程被释放后,屏障点的数值可以再次被重置
CyclicBarrier
一般被称为栅栏
CyclicBarrier
是一种同步机制,允许一组线程互相等待。现成的达到屏障点其实是基于await方法在屏障点阻塞
CyclicBarrier
并没有基于AQS实现,他是基于ReentrantLock
锁的机制去实现了对屏障点--
,以及线程挂起的操作(CountDownLatch
本身是基于AQS,对state
进行release
操作后,可以-1
)
CyclicBarrier
没来一个线程执行await
,都会对屏障数值进行-1
操作,每次-1
后,立即查看数值是否为0
,如果为0
,直接唤醒所有的互相等待线程
CyclicBarrier对比CountDownLatch区别
- 底层实现不同。
CyclicBarrier
基于ReentrantLock
做的。CountDownLatch
直接基于AQS做的 - 应用场景不同。
CountDownLatch
的计数器只能使用一次。而CyclicBarrier
在计数器达到0
之后,可以重置计数器。CyclicBarrier
可以实现相比CountDownLatch
更复杂的业务,执行业务时出现了错误,可以重置CyclicBarrier
计数器,再次执行一次 CyclicBarrier
还提供了很多其他的功能:- 可以获取到阻塞的现成有多少
- 在线程互相等待时,如果有等待的线程中断,可以抛出异常,避免无限等待的问题。
CountDownLatch
一般是让主线程等待,让子线程对计数器--
。CyclicBarrier
更多的让子线程也一起计数和等待,等待的线程达到数值后,再统一唤醒
CyclicBarrier
:多个线程互相等待,直到到达同一个同步点,再一次执行
2.2 CyclicBarrier应用
出国旅游
导游小姐姐需要等待所有乘客都到位后,发送护照,签证等等文件,再一起出发
比如Tom,Jack,Rose三个人组个团出门旅游
在构建CyclicBarrier
可以指定barrierAction
,可以选择性指定,如果指定了,那么会在barrier
归0
后,优先执行barrierAction
任务,然后再去唤醒所有阻塞挂起的线程,并行去处理后续任务
所有互相等待的线程,可以指定等待时间,并且在等待的过程中,如果有线程中断,所有互相的等待的线程都会被唤醒
public static void main(String[] args) throws InterruptedException {
CyclicBarrier barrier = new CyclicBarrier(3, () -> {
System.out.println("等到各位大佬都到位之后,分发护照和签证等内容!");
});
new Thread(() -> {
System.out.println("Tom到位!!!");
try {
barrier.await();
} catch (Exception e) {
System.out.println("悲剧,人没到齐!");
return;
}
System.out.println("Tom出发!!!");
}).start();
Thread.sleep(100);
new Thread(() -> {
System.out.println("Jack到位!!!");
try {
barrier.await();
} catch (Exception e) {
System.out.println("悲剧,人没到齐!");
return;
}
System.out.println("Jack出发!!!");
}).start();
Thread.sleep(100);
new Thread(() -> {
System.out.println("Rose到位!!!");
try {
barrier.await();
} catch (Exception e) {
System.out.println("悲剧,人没到齐!");
return;
}
System.out.println("Rose出发!!!");
}).start();
}
console
Tom到位!!!
Jack到位!!!
Rose到位!!!
等到各位大佬都到位之后,分发护照和签证等内容!
Rose出发!!!
Tom出发!!!
Jack出发!!!
Process finished with exit code 0
CyclicBarrier的几种使用情况
- 如果在等待期间,有线程中断了,唤醒所有线程后,
CyclicBarrier
无法继续使用,线程中断后,需要继续使用当前的CyclicBarrier
,需要调用reset
方法,让CyclicBarrier
重置- 如果
CyclicBarrier
的屏障数值到达0
之后,他默认会重置屏障数值,CyclicBarrier
在没有线程中断时,是可以重复使用的- 如果某线程任务
await
设置等待时间超过等待时间程序还未执行,则所有调用CyclicBarrier
对象的任务都会终止,然后执行catch
里面的业务,且需要继续使用当前的CyclicBarrier
,需要调用reset
方法,让CyclicBarrier
重置
2.3 CyclicBarrier源码分析
分成两块内容去查看,首先查看CyclicBarrier
的一些核心属性,然后再查看CyclicBarrier
的核心方法
2.3.1 CyclicBarrier的核心属性
public class CyclicBarrier {
// 这个静态内部类是用来标记是否中断的
private static class Generation {
boolean broken = false;
}
/** CyclicBarrier是基于ReentrantLock实现的互斥操作,以及计数原子性操作 */
private final ReentrantLock lock = new ReentrantLock();
/** 基于当前的Condition实现线程的挂起和唤醒 */
private final Condition trip = lock.newCondition();
/** 记录有参构造传入的屏障数值,不会对这个数值做操作 */
private final int parties;
/** 当屏障数值达到0之后,优先执行当前任务 */
private final Runnable barrierCommand;
/** 初始化默认的Generation,用来标记线程中断情况 */
private Generation generation = new Generation();
/** 每来一个线程等待,就对count进行-- */
private int count;
}
2.3.2 CyclicBarrier的有参构造
掌握构建CyclicBarrier
之后,内部属性的情况
// 这个是CyclicBarrier的有参构造
// 在内部传入了parties,屏障点的数值
// 还传入了barrierAction,屏障点的数值达到0,优先执行barrierAction任务
public CyclicBarrier(int parties, Runnable barrierAction) {
// 健壮性判
if (parties <= 0) throw new IllegalArgumentException();
// 当前类中的属性parties是保存屏障点数值的
this.parties = parties;
// 将parties赋值给属性count,每来一个线程,基于count做-1操作。
this.count = parties;
// 优先执行的任务
this.barrierCommand = barrierAction;
}
2.3.3 CyclicBarrier中的await方法
在CyclicBarrier
中,提供了2个await
方法
- 第一个是无参的方式,线程要死等,直到屏障点数值为
0
,或者有线程中断 - 第二个是有参方式,传入等待的时间,要么时间到位了,要不就是直到屏障点数值为
0
,或者有线程中断
无论是哪种await
方法,核心都在于内部调用的dowait
方法
dowait
方法主要包含了线程互相等待的逻辑,以及屏障点数值到达0
之后的操作
// 包含了线程互相等到的逻辑,以及屏障点数值到达0后的操作
private int dowait(boolean timed, long nanos)throws
// 当前新编程中断,抛出这个异常
InterruptedException,
// 其他线程中断,当前线程抛出这个异常
BrokenBarrierException,
// await时间到位,抛出这个异常
TimeoutException {
// 加锁
final ReentrantLock lock = this.lock;
lock.lock();
try {
// 拿到Generation对象的引用
final Generation g = generation;
// 判断下线程中断了么?如果中断了,直接抛出异常
if (g.broken)
throw new BrokenBarrierException();
// 当前线程中断了么?
if (Thread.interrupted()) {
// 做了三个实现,
// 设置broken为true,将count重置,唤醒其他等待的线程
breakBarrier();
// 抛出异常
throw new InterruptedException();
}
// 屏障点做--
int index = --count;
// 如果屏障点为0,打开屏障啦!!
if (index == 0) {
// 标记
boolean ranAction = false;
try {
// 拿到有参构造中传递的任务
final Runnable command = barrierCommand;
// 任务不为null,优先执行当前任务
if (command != null)
command.run();
// 上述任务执行没问题,标记位设置为true
ranAction = true;
// 执行nextGeneration
// 唤醒所有线程,重置count,重置generation
nextGeneration();
return 0;
} finally {
// 如果优先执行的任务出了问题i,就直接抛出异常
if (!ranAction)
breakBarrier();
}
}
// 死循环
for (;;) {
try {
// 如果调用await方法,死等
if (!timed)
trip.await();
// 如果调用await(time,unit),基于设置的nans时长决定await的时长
else if (nanos > 0L)
nanos = trip.awaitNanos(nanos);
} catch (InterruptedException ie) {
// 到这,说明线程被中断了
// 查看generation有没有被重置。
// 并且当前broken为false,需要做线程中断后的操作。
if (g == generation && ! g.broken) {
breakBarrier();
throw ie;
} else {
Thread.currentThread().interrupt();
}
}
// 是否是中断唤醒,是就抛异常。
if (g.broken)
throw new BrokenBarrierException();
// 说明被reset了,返回index的数值。或者任务完毕也会被重置
if (g != generation)
return index;
// 指定了等待的时间内,没有等到所有线程都到位
if (timed && nanos <= 0L) {
// 中断任务
breakBarrier();
// 抛出异常
throw new TimeoutException();
}
}
} finally {
lock.unlock();
}
}
三、Semaphone应用&源码分析
3.1 Semaphore介绍
Synchronized
,ReentrantLock
是互斥锁,保证一个资源同一时间只允许被一个线程访问
Semaphore
(信号量)保证1个或多个资源可以被指定数量的线程同时访问
底层实现是基于AQS去做的
Semaphore
底层也是基于AQS的state
属性做一个计数器的维护。state
的值就代表当前共享资源的个数。如果一个线程需要获取的1个或多个资源,直接查看state
的标识的资源个数是否足够,如果足够的,直接对state - 1
拿到当前资源。如果资源不够,当前线程就需要挂起等待。知道持有资源的线程释放资源后,会归还给Semaphore
中的state
属性,挂起的线程就可以被唤醒
Semaphore
也分为公平和非公平的概念
使用场景:连接池对象就可以基础信号量去实现管理。在一些流量控制上,也可以采用信号量去实现。再比如去迪士尼或者是环球影城,每天接受的人流量是固定的,指定一个具体的人流量,可能接受10000人,每有一个人购票后,就对信号量进行--
操作,如果信号量已经达到了0,或者是资源不足,此时就不能买票
3.2 Semaphore应用
以上面环球影城每日人流量为例子去测试一下
public static void main(String[] args) throws InterruptedException {
// 今天环球影城还有人个人流量
Semaphore semaphore = new Semaphore(10);
new Thread(() -> {
try {
System.out.println("一家三口要去~~");
semaphore.acquire(3);
System.out.println("一家三口进去了~~~");
Thread.sleep(10000);
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
semaphore.release(3);
System.out.println("一家三口走了~~~");
}
}).start();
for (int i = 0; i < 7; i++) {
int j = i;
new Thread(() -> {
try {
System.out.println(j + "大哥来了。");
semaphore.acquire();
System.out.println(j + "大哥进去了~~~");
Thread.sleep(10000);
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
semaphore.release();
System.out.println(j + "大哥走了~~~");
}
}).start();
}
Thread.sleep(10);
System.out.println("main大哥来了。");
if (semaphore.tryAcquire(10, TimeUnit.SECONDS)) {
System.out.println("main大哥进来了。");
} else {
System.out.println("资源不够,main大哥没进来。");
}
Thread.sleep(10000);
System.out.println("main大哥又来了。");
if (semaphore.tryAcquire()) {
System.out.println("main大哥进来了。");
semaphore.release();
} else {
System.out.println("资源不够,main大哥没进来。");
}
}
其实Semaphore
整体就是对构建Semaphore
时,指定的资源数的获取和释放操作
获取资源方式:
- acquire():获取一个资源,没有资源就挂起等待,如果中断,直接抛异常
- acquire(int):获取指定个数资源,资源不够,或者没有资源就挂起等待,如果中断,直接抛异常
- tryAcquire():获取一个资源,没有资源返回
false
,有资源返回true
- tryAcquire(int):获取指定个数资源,没有资源返回
false
,有资源返回true
- tryAcquire(time,unit):获取一个资源,如果没有资源,等待
time.unit
,如果还没有,就返回false
- tryAcquire(int,time,unit):获取指定个数资源,如果没有资源,等待
time.unit
,如果还没有,就返回false
- acquireUninterruptibly():获取一个资源,没有资源就挂起等待,中断线程不结束,继续等
- acquireUninterruptibly(int):获取指定个数资源,没有资源就挂起等待,中断线程不结束,继续等
归还资源方式:
- release():归还一个资源
- release(int):归还指定个数资源
3.3 Semaphore源码分析
先查看Semaphore
的整体结构,然后基于获取资源,以及归还资源的方式去查看源码
3.3.1 Semaphore的整体结构
Semaphore
内部有3个静态内类
首先是向上抽取的Sync
其次还有两个Sync
的子类NonFairSync
以及FairSync
两个静态内部类
Sync
内部主要提供了一些公共的方法,并且将有参构造传入的资源个数,直接基于AQS提供的setState
方法设置了state属性。
NonFairSync
以及FairSync
区别就是tryAcquireShared
方法的实现是不一样
3.3.2 Semaphore的非公平的获取资源
在构建Semaphore
的时候,如果只设置资源个数,默认情况下是非公平
如果在构建Semaphore
,传入了资源个数以及一个boolean
时,可以选择非公平还是公平
public Semaphore(int permits, boolean fair) {
sync = fair ? new FairSync(permits) : new NonfairSync(permits);
}
从非公平的acquire
方法入手
首先确认默认获取资源数是1个,并且acquire
是允许中断线程时,抛出异常的。获取资源的方式,就是直接用state
-
需要的资源数,只要资源足够,就CAS的将state
做修改。如果没有拿到锁资源,就基于共享锁的方式去将当前线程挂起在AQS双向链表中。如果基于doAcquireSharedInterruptibly
拿锁成功,会做一个事情。会执行setHeadAndPropagate方法。一会说
// 信号量的获取资源方法(默认获取一个资源)
public void acquire() throws InterruptedException {
// 跳转到了AQS中提供共享锁的方法
sync.acquireSharedInterruptibly(1);
}
// AQS提供的
public final void acquireSharedInterruptibly(int arg) throws InterruptedException {
// 判断线程的中断标记位,如果已经中断,直接抛出异常
if (Thread.interrupted())
throw new InterruptedException();
// 先看非公平的tryAcquireShared实现。
// tryAcquireShared:
// 返回小于0,代表获取资源失败,需要排队。
// 返回大于等于0,代表获取资源成功,直接执行业务代码
if (tryAcquireShared(arg) < 0)
doAcquireSharedInterruptibly(arg);
}
// 信号量的非公平获取资源方法
final int nonfairTryAcquireShared(int acquires) {
// 死循环。
for (;;) {
// 获取state的数值,剩余的资源个数
int available = getState();
// 剩余的资源个数 - 需要的资源个数
int remaining = available - acquires;
// 如果-完后,资源个数小于0,直接返回这个负数
if (remaining < 0 ||
// 说明资源足够,基于CAS的方式,将state从原值,改为remaining
compareAndSetState(available, remaining))
return remaining;
}
}
// 获取资源失败,资源不够,当前线程需要挂起等待
private void doAcquireSharedInterruptibly(int arg) throws InterruptedException {
// 构建Node节点,线程和共享锁标记,并且到AQS双向链表中
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;
failed = false;
return;
}
}
// 如果上面没拿到,或者不是head的next节点,将前继节点的状态改为-1,并挂起当前线程
if (shouldParkAfterFailedAcquire(p, node) && parkAndCheckInterrupt())
// 如果线程中断会抛出异常
throw new InterruptedException();
}
} finally {
if (failed)
cancelAcquire(node);
}
}
acquire()
以及acquire(int)
的方式,都是执行acquireSharedInterruptibly
方法去尝试获取资源,区别只在于是否传入了需要获取的资源个数
tryAcquire()
以及tryAcquire(int)
因为这两种方法是直接执行tryAcquire
,只使用非公平的实现,只有非公平的情况下,才有可能在有线程排队的时候获取到资源
但是tryAcquire(int,time,unit)
这种方法是正常走的AQS提供的acquire
。因为这个tryAcquire
可以排队一会,即便是公平锁也有可能拿到资源。这里的挂起和acquire
挂起的区别仅仅是挂起的时间问题
acquire
是一直挂起直到线程中断,或者线程被唤醒tryAcquire(int,time,unit)
是挂起一段时间,直到线程中断,要么线程被唤醒,要么阻塞时间到了
还有acquireUninterruptibly()
以及acquireUninterruptibly(int)
只是在挂起线程后,不会因为线程的中断而去抛出异常
3.3.3 Semaphore公平实现
公平与非公平只是差了一个方法的实现tryAcquireShared
实现
这个方法的实现中,如果是公平实现,需要先查看AQS中排队的情况
// 信号量公平实现
protected int tryAcquireShared(int acquires) {
// 死循环。
for (;;) {
// 公平实现在走下述逻辑前,先判断队列中排队的情况
// 如果没有排队的节点,直接不走if逻辑
// 如果有排队的节点,发现当前节点处在head.next位置,直接不走if逻辑
if (hasQueuedPredecessors())
return -1;
// 下面这套逻辑和公平实现是一模一样的。
int available = getState();
int remaining = available - acquires;
if (remaining < 0 ||
compareAndSetState(available, remaining))
return remaining;
}
}
3.3.4 Semaphore释放资源
因为信号量从头到尾都是共享锁的实现……
释放资源操作,不区分公平和非公平
// 信号量释放资源的方法入口
public void release() {
sync.releaseShared(1);
}
// 释放资源不分公平和非公平,都走AQS的releaseShared
public final boolean releaseShared(int arg) {
// 优先查看tryReleaseShared,这个方法是信号量自行实现的。
if (tryReleaseShared(arg)) {
// 只要释放资源成功,执行doReleaseShared,唤醒AQS中排队的线程,去竞争Semaphore的资源
doReleaseShared();
return true;
}
return false;
}
// 信号量实现的释放资源方法
protected final boolean tryReleaseShared(int releases) {
// 死循环
for (;;) {
// 拿到当前的state
int current = getState();
// 将state + 归还的资源个数,新的state要被设置为next
int next = current + releases;
// 如果归还后的资源个数,小于之前的资源数。
// 避免出现归还资源后,导致next为负数,需要做健壮性判断
if (next < current)
throw new Error("Maximum permit count exceeded");
// CAS操作,保证原子性,只会有一个线程成功的就之前的state修改为next
if (compareAndSetState(current, next))
return true;
}
}
3.4 AQS中PROPAGATE节点
为了更好的了解PROPAGATE
节点状态的意义,优先从JDK1.5去分析一下释放资源以及排队后获取资源的后置操作
3.4.1 掌握JDK1.5-Semaphore执行流程图
首先查看4个线程获取信号量资源的情况
往下查看释放资源的过程会触发什么问题
首先t1释放资源,做了进一步处理
当线程3获取锁资源后,线程2再次释放资源,因为执行点问题,导致线程4无法被唤醒
3.4.2 分析JDK1.8的变化
====================================JDK1.5实============================================.
public final boolean releaseShared(int arg) {
if (tryReleaseShared(arg)) {
Node h = head;
if (h != null && h.waitStatus != 0)
// 该方法类似JDK1.8的doReleaseShared方法,但少了其中else if的信号量状态(PROPAGATE)的判断
unparkSuccessor(h);
return true;
}
return false;
}
private void setHeadAndPropagate(Node node, int propagate) {
setHead(node);
if (propagate > 0 && node.waitStatus != 0) {
Node s = node.next;
if (s == null || s.isShared())
unparkSuccessor(node);
}
}
====================================JDK1.8实============================================.
public final boolean releaseShared(int arg) {
if (tryReleaseShared(arg)) {
doReleaseShared();
return true;
}
return false;
}
private void doReleaseShared() {
for (;;) {
// 拿到head节点
Node h = head;
// 判断AQS中有排队的Node节点
if (h != null && h != tail) {
// 拿到head节点的状态
int ws = h.waitStatus;
// 状态为-1
if (ws == Node.SIGNAL) {
// 将head节点的状态从-1,改为0
if (!compareAndSetWaitStatus(h, Node.SIGNAL, 0))
continue;
// 唤醒后继节点
unparkSuccessor(h);
}
// 发现head状态为0,将head状态从0改为-3,目的是为了往后面传播
else if (ws == 0 &&
!compareAndSetWaitStatus(h, 0, Node.PROPAGATE))
continue; // loop on failed CAS
}
// 没有并发的时候。head节点没变化,正常完成释放排队的线程
if (h == head)
break;
}
}
private void setHeadAndPropagate(Node node, int propagate) {
// 拿到head
Node h = head;
// 将线程3的Node设置为新的head
setHead(node);
// 如果propagate 大于0,代表还有剩余资源,直接唤醒后续节点,如果不满足,也需要继续往后判断看下是否需要传播
// h == null:看成健壮性判断即可
// 之前的head节点状态为负数,说明并发情况下,可能还有资源,需要继续向后唤醒Node
// 如果当前新head节点的状态为负数,继续释放后续节点
if (propagate > 0 || h == null || h.waitStatus < 0 || (h = head) == null || h.waitStatus < 0) {
// 唤醒当前节点的后继节点
Node s = node.next;
if (s == null || s.isShared())
doReleaseShared();
}
}