CountDownLatch
源码解析
CountDownLatch主要是两个方法:await()、countDown(),还有一个构造方法 CountDownLatch(int count)
构造方法:CountDownLatch(int count)
public CountDownLatch(int count) {
if (count < 0) throw new IllegalArgumentException("count < 0");
// 实例化一个Sync对象
this.sync = new Sync(count);
}
通过构造方法去设置AQS state的初始值为count,Sync是在CountDownLatch类中实现的AQS实现类
阻塞方法:await()
await()会阻塞主线程,直到所有线程都countDown()数量等于count,也就是state==0
public void await() throws InterruptedException {
// 共享式获取AQS的同步状态
sync.acquireSharedInterruptibly(1);
}
调用的是AQS的acquireSharedInterruptibly方法:
public final void acquireSharedInterruptibly(int arg)
throws InterruptedException {
if (Thread.interrupted()) // 线程中断 说明闭锁对线程中断敏感
throw new InterruptedException();
if (tryAcquireShared(arg) < 0) // 闭锁未使用完成 线程进入同步队列自旋等待
doAcquireSharedInterruptibly(arg);
}
tryAcquireShared(int acquires):
/** 获取共享锁 */
protected int tryAcquireShared(int acquires) {
// AQS的同步状态为0则闭锁结束 可以进行下一步操作
return (getState() == 0) ? 1 : -1;
}
也就是需要当getState() == 0的时候,才可以进行继续执行,否则线程进入同步队列自旋等待(AQS同步队列的自旋等待)
计数方法:countDown()
调用countDown()方法会将计数器减1,直到计数器为0,代码如下:
public void countDown() {
sync.releaseShared(1);
}
同样调用的是AQS的releaseShared方法:
public final boolean releaseShared(int arg) {
if (tryReleaseShared(arg)) { // 减少闭锁的计数器,只有计数器为0的时候才会返回true
doReleaseShared(); // 唤醒被await方法阻塞的所有线程
return true;
}
return false;
}
这里的返回这对CountDownLatch没有用,其中tryReleaseShared方法依赖的是Sync的实现:
/** 释放共享锁 */
protected boolean tryReleaseShared(int releases) {
// 死循环,如果CAS操作失败就会不断继续尝试
for (;;) {
int c = getState();
if (c == 0) // 正常不会进入此逻辑
return false;
int nextc = c-1; // 将计数器-1
if (compareAndSetState(c, nextc)) // 更新计数器
// 如果操作成功,返回计数器是否为0,直接关系到是否执行doReleaseShared方法来唤醒后续线程
return nextc == 0;
}
}
可以看到,只有当计数器等于0的时候才会返回true,才会唤醒后续线程(调用await()自旋等待的线程)
总结
- 在await()的时候,调用await()的线程将进入自旋等待,自旋过程会调用AQS的Sync实现的tryAcquireShared方法,只有当闭锁计数器等于0的时候,线程才能够继续执行
- 在其它线程调用countDown的方法时候,会将闭锁计数器减1(state-1),直至计数器等于0的时候,会唤醒后续线程获取闭锁(doReleaseShared),自旋等待中的线程才可以继续执行
Semaphore源码解析
关键方法如下:
- 构造方法:new Semaphore(5);
- 获取许可:semaphore.acquire();
- 释放许可:semaphore.release();
接下来我们从这三个方法入手进行源码解析
构造方法:new Semaphore(5)
public Semaphore(int permits) {
sync = new NonfairSync(permits);
}
public Semaphore(int permits, boolean fair) {
sync = fair ? new FairSync(permits) : new NonfairSync(permits);
}
默认构造方法为非公平共享锁,可以通过构造参数fair来选择公平或非公平,类似于ReentantLock
获取许可:semaphore.acquire()
public void acquire() throws InterruptedException {
// 共享式获取AQS的同步状态
sync.acquireSharedInterruptibly(1);
}
调用的是AQS的acquireSharedInterruptibly方法:
public final void acquireSharedInterruptibly(int arg)
throws InterruptedException {
if (Thread.interrupted()) {
throw new InterruptedException();
}
if (tryAcquireShared(arg) < 0) {
doAcquireSharedInterruptibly(arg);
}
}
其中tryAcquireShared依赖于Sync实现,在Semaphore中有AQS的实现Sync类,方法如下:
// 尝试获取共享锁
protected int tryAcquireShared(int acquires) {
for (; ; ) {
// 队列中存在等待线程则返回-1
if (hasQueuedPredecessors())
return -1;
int available = getState(); // 可用许可数量
int remaining = available - acquires; // 剩余许可数量
if (remaining < 0 || compareAndSetState(available, remaining))
// 返回可用的余量
return remaining;
}
}
这是FairSync的tryAcquireShared方法,在NonfairSync中,没有hasQueuedPredecessors()判断,其余一样。
在方法中可以看出,最终返回的是剩余的许可数量,有如下几种情况:
private void doAcquireSharedInterruptibly(int arg)
throws InterruptedException {
// 线程进入同步队列
final Node node = addWaiter(Node.SHARED);
boolean failed = true;
try {
for (; ; ) { // 自旋
final Node p = node.predecessor();
if (p == head) { // 当前节点的前置节点是AQS的头节点 即自己是AQS同步队列的第一个节点
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); // 获取失败 就取消获取
}
}
}
- 如果剩余许可数量<0,则执行doAcquireSharedInterruptibly方法让线程自旋等待,这里是等待别的线程释放许可后线程被唤醒去尝试获取
- 否则就是拿到了许可数量,继续正常执行,不阻塞
释放许可:semaphore.release()
public void release() {
sync.releaseShared(1);
}
同样,调用的是AQS的releaseShared方法,看下代码:
public final boolean releaseShared(int arg) {
// 调用AQS实现类的tryReleaseShared
if (tryReleaseShared(arg)) {
// 唤醒后续的线程节点
doReleaseShared();
return true;
}
return false;
}
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)) // CAS更新当前信号量许可数
return true;
}
}
释放许可成功则继续调用AQS的doReleaseShared方法来唤醒后续节点可以来争取许可了
private void doReleaseShared() {
for (; ; ) { // 自旋等待
Node h = head;
// 有头节点且头节点和尾节点不是同一个
if (h != null && h != tail) {
int ws = h.waitStatus;
if (ws == Node.SIGNAL) {
// 设置status为0
if (!compareAndSetWaitStatus(h, Node.SIGNAL, 0)) {
continue; // 循环检查
}
// 唤醒节点的后续节点
unparkSuccessor(h);
} else if (ws == 0 && !compareAndSetWaitStatus(h, 0, Node.PROPAGATE)) { //
continue; // 失败则继续循环
}
}
if (h == head) {
break;
}
}
}
总结
Semaphore使用AQS同步状态来保存信号量的计数器。
acquireSharedInterruptibly会减少计数(获取许可),当计数为非正值的时候阻塞线程,否则不会阻塞线程
releaseShared方法会增加计数(释放许可),在计数不超过信号量限制时会解除线程的阻塞(获取到许可的线程)
阻塞方法:cyclicBarrier.await()
核心dowait(boolean timed, long nanos)
private int dowait(boolean timed, long nanos)
throws InterruptedException, BrokenBarrierException,
TimeoutException {
// 使用独占锁来执行dowait方法,并发性可能不是很高
final ReentrantLock lock = this.lock;
lock.lock();
try {
// 当前代
final Generation g = generation;
// 如果当前代损坏了则抛出异常
if (g.broken)
throw new BrokenBarrierException();
// 如果线程中断则抛出异常
if (Thread.interrupted()) {
// 将损坏状态设置为true,并通知其他阻塞在此栅栏上的线程
breakBarrier();
throw new InterruptedException();
}
// 获取下标
int index = --count;
// 如果是 0,说明最后一个线程调用了该方法
if (index == 0) { // tripped
boolean ranAction = false;
try {
final Runnable command = barrierCommand;
// 执行栅栏任务
if (command != null)
command.run();
ranAction = true;
// 更新一代,将count重置,将generation重置
nextGeneration();
return 0;
} finally {
// 如果执行栅栏任务的时候失败了,就将损坏状态设置为true
if (!ranAction)
breakBarrier();
}
}
// 自旋直到触发、broken、中断或超时
for (; ; ) {
try {
// 如果没有时间限制,则直接等待,直到被唤醒
if (!timed)
trip.await();
else if (nanos > 0L) // 如果有时间限制,则等待指定时间
nanos = trip.awaitNanos(nanos);
} catch (InterruptedException ie) {
// 当前代没有损坏
if (g == generation && !g.broken) {
breakBarrier(); // 让栅栏失效
throw ie;
} else {
// 上面条件不满足,说明这个线程不是这代的, 就不会影响当前这代栅栏的执行,所以,就打个中断标记
Thread.currentThread().interrupt();
}
}
// 当有任何一个线程中断了,就会调用breakBarrier方法,就会唤醒其他的线程,其他线程醒来后,也要抛出异常
if (g.broken)
throw new BrokenBarrierException();
// g != generation表示正常换代了,返回当前线程所在栅栏的下标
// 如果 g == generation,说明还没有换代,那为什么会醒了?
// 因为一个线程可以使用多个栅栏,当别的栅栏唤醒了这个线程,就会走到这里,所以需要判断是否是当前代。
// 正是因为这个原因,才需要generation来保证正确。
if (g != generation)
return index;
// 如果有时间限制,且时间小于等于0,销毁栅栏并抛出超时异常
if (timed && nanos <= 0L) {
breakBarrier();
throw new TimeoutException();
}
}
} finally {
lock.unlock();
}
}
-
执行一些验证:栅栏是否broken、线程是否中断
-
如果是最后一个线程调用dowait,则执行栅栏任务barrierAction,然后更新代nextGeneration
-
如果不是最后一个线程调用dowait,则自旋,trip.await()会进行阻塞,直至发生如下情况才会被唤醒或终止:
1.最后一个线程到达,即index==0
2.某个参与栅栏的线程等待超时
3.某个参与栅栏的线程被中断
4.调用了CyclicBarrier的reset()方法,该方法会将屏障置为初始状态 -
在被唤醒之后,栅栏没有损坏且是同一代,则返回下标index
https://blog.youkuaiyun.com/qq_32828253/article/details/111936100?spm=1001.2014.3001.5502
https://blog.youkuaiyun.com/qq_32828253/article/details/111937784?spm=1001.2014.3001.5502
https://blog.youkuaiyun.com/qq_32828253/article/details/111934945?spm=1001.2014.3001.5502