一、CountDownLatch
1.1 简单介绍
CountDownLatch 就是 JUC 包下的一个工具,这个工具最核心的功能就是计数器。如果有三个业务需要并行处理,并且需要知道三个业务全部都处理完毕了,那么需要一个并发安全的计数器来操作。
CountDownLatch 就可以实现这个需求。给 CountDownLatch 设置一个数值,可以设置 3。
每个业务处理完毕之后,执行一次 countDown 方法,指定的 3 每次在执行 countDown 方法时,对 3 进行 -1。
主线程可以在业务处理时,执行 await,主线程会阻塞等待任务处理完毕。当设置的 3 基于 countDown 方法减为 0 之后,主线程就会被唤醒,继续处理后续业务。
当业务中出现 2 个以上允许并行处理的任务,并且需要在任务都处理完毕后,再做其他处理时,可以采用 CountDownLatch 去实现这个功能。
1.2 简单应用
模拟有三个任务需要并行处理,在三个任务全部处理完毕后,再执行后续操作。CountDownLatch 中,执行 countDown 方法,代表一个任务结束,对计数器 -1。执行 await 方法,代表等待计数器变为 0 时,再继续执行。
执行 await(time, unit) 方法,代表等待 time 时长,如果计数器不为 0,返回 false,如果在等待期间,计数器为 0,方法就返回 true。
一般 CountDownLatch 更多的是基于业务去构建,不采用成员变量。
示例代码:
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.Executors;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;
public class Main {
public static ThreadPoolExecutor executor = (ThreadPoolExecutor) Executors.newFixedThreadPool(3);
public static CountDownLatch countDownLatch = new CountDownLatch(3);
public static void main(String[] args) throws InterruptedException {
System.out.println("主业务开始执行");
sleep(1000);
executor.execute(Main::a);
executor.execute(Main::b);
executor.execute(Main::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 的内部类 Sync 继承了 AQS,CountDownLatch 就是基于 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,就是代表循环屏障。
让一个或多个线程到达一个屏障点,会被阻塞。屏障点会有一个数值,当一个线程阻塞在屏障点时,就会对屏障点的数值进行 -1 操作,当屏障点数值减为 0 时,屏障就会打开,唤醒所有阻塞在屏障点的线程。在释放屏障点之后,可以先执行一个任务,再让所有阻塞被唤醒的线程继续之后后续任务。
所有线程被释放后,屏障点的数值可以再次被重置。CyclicBarrier 一般被称为栅栏。
CyclicBarrier 是一种同步机制,允许一组线程互相等待。现成的达到屏障点其实是基于 await 方法在屏障点阻塞。
CyclicBarrier 并没有基于 AQS 实现,他是基于 ReentrantLock 锁的机制去实现了对屏障点 -1,以及线程挂起的操作。(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一般是让主线程等待,让子线程对计数器-1。CyclicBarrier更多的让子线程也一起计数和等待,等待的线程达到数值后,再统一唤醒。CyclicBarrier可以实现多个线程互相等待,直到到达同一个同步点,再一次执行。
2.2 简单应用
以出国旅游为例,导游小姐姐需要等待所有乘客都到位后,发送护照、签证等等文件,再一起出发。
比如 Tom、Jack、Rose 三个人组个团出门旅游。
在构建 CyclicBarrier 可以指定 barrierAction,也可以选择性指定,如果指定了,那么会在 barrier 归 0 后,优先执行 barrierAction 任务,然后再去唤醒所有阻塞挂起的线程,并行去处理后续任务。
所有互相等待的线程,可以指定等待时间,并且在等待的过程中,如果有线程中断,所有互相的等待的线程都会被唤醒。
如果在等待期间,有线程中断了,唤醒所有线程后,CyclicBarrier 无法继续使用。
如果线程中断后,需要继续使用当前的 CyclicBarrier,需要调用 reset 方法,让 CyclicBarrier 重置。
如果 CyclicBarrier 的屏障数值到达 0 之后,他默认会重置屏障数值,CyclicBarrier 在没有线程中断时,是可以重复使用的。
public class Main {
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();
}
}
2.3 源码分析
2.3.1 核心属性
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的有参构造
// 在内部传入了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 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();
}
}
三、Semaphore
3.1 简单介绍
Semaphore(信号量)保证 1 个或多个资源可以被指定数量的线程同时访问。底层实现是基于 AQS 去做的。
Semaphore 底层也是基于 AQS 的 state 属性做一个计数器的维护。state 的值就代表当前共享资源的个数。如果一个线程需要获取的 1 或多个资源,直接查看 state 的标识的资源个数是否足够,如果足够的,直接对 state - 1 拿到当前资源。如果资源不够,当前线程就需要挂起等待。直到持有资源的线程释放资源后,会归还给 Semaphore 中的 state 属性,挂起的线程就可以被唤醒。
Semaphore 也分为公平和非公平的概念。
使用场景:连接池对象就可以基础信号量去实现管理。在一些流量控制上,也可以采用信号量去实现。再比如去迪士尼或者是环球影城,每天接受的人流量是固定的,指定一个具体的人流量,可能接受 人,每有一个人购票后,就对信号量进行 -1 操作,如果信号量已经达到了 ,或者是资源不足,此时就不能买票。
3.2 简单应用
以上面环球影城每日人流量为例子去测试一下:
import java.util.concurrent.Semaphore;
public class Main {
public static void main(String[] args) throws InterruptedException {
// 今天环球影城还有人个人流量
Semaphore semaphore = new Semaphore(10);
new Thread(() -> {
System.out.println("一家三口要去");
try {
semaphore.acquire(3);
System.out.println("一家三口进去了");
Thread.sleep(10000);
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
System.out.println("一家三口走了");
semaphore.release(3);
}
}).start();
for (int i = 1; i <= 7; i++) {
int j = i;
new Thread(() -> {
System.out.println(j + "号大哥来了。");
try {
semaphore.acquire();
System.out.println(j + "大哥进去了");
Thread.sleep(10000);
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
System.out.println(j + "大哥走了");
semaphore.release();
}
}).start();
}
Thread.sleep(10);
System.out.println("main大哥来了。");
if (semaphore.tryAcquire()) {
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 时间,如果还没有,就返回 false。tryAcquire(int, time, unit):获取指定个数资源,如果没有资源,等待 time 时间,如果还没有,就返回false。acquireUninterruptibly():获取一个资源,没有资源就挂起等待,中断线程不结束,继续等。acquireUninterruptibly(int):获取指定个数资源,没有资源就挂起等待,中断线程不结束,继续等。
归还资源方式:
release():归还一个资源。release(int):归还指定个数资源。
3.3 源码分析
3.3.1 整体结构
Semaphore 内部有 3 个静态内类。首先是向上抽取的 Sync。
其次还有两个 Sync 的子类 NonFairSync 以及 FairSync 两个静态内部类。
Sync 内部主要提供了一些公共的方法,并且将有参构造传入的资源个数,直接基于 AQS 提供的 setState 方法设置了 state 属性。
NonFairSync 以及 FairSync 区别就是 tryAcquireShared 方法的实现是不一样。
3.3.2 非公平获取资源
在构建 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();
// 如果是head.next,就抢一手
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 公平实现
公平与非公平只是差了一个方法的实现 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 释放资源
// 信号量释放资源的方法入口
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节点
3.4.1 Java 5中的执行流程图
首先查看 4 个线程获取信号量资源的情况:
往下查看释放资源的过程会触发什么问题,首先 t1 释放资源,做了进一步处理。
当线程 3 获取锁资源后,线程 2 再次释放资源,因为执行点问题,导致线程 4 无法被唤醒。
3.4.2 Java 8中的变化
====================================JDK1.5实现============================================.
public final boolean releaseShared(int arg) {
if (tryReleaseShared(arg)) {
Node h = head;
if (h != null && h.waitStatus != 0)
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();
}
}