0
点赞
收藏
分享

微信扫一扫

AQS之CountDownLatch分析 (八)


文章目录

  • ​​1.CountDownLatch 介绍​​
  • ​​2.实例代码​​
  • ​​3.源码分析​​
  • ​​3.1 构造器​​
  • ​​3.2 获取资源 await​​
  • ​​3.3 释放资源 countDown​​
  • ​​3.4 源码总结​​

1.CountDownLatch 介绍

CountDownLatch即减少计数,是AQS共享锁的另一个经典应用。其应用主要是一个(或多个)线程等待一系列线程完成某些操作后才继续向下执行的场景。

换种程序上的描述:A线程申请资源await,进行阻塞等待,一系列线程进行某些操作(共state个),每完成一个释放一次资源coutDown。所有操作完成后,A线程资源获取成功,继续向下执行。

AQS之CountDownLatch分析 (八)_释放资源

2.实例代码

6个线程全部离开后, 进行main线程

public static void main(String[] args) throws InterruptedException {
// 减少计数
CountDownLatch countDownLatch = new CountDownLatch(6);

for (int i = 1; i <= 6; i++) {
new Thread(()->{
countDownLatch.countDown();
System.out.println(Thread.currentThread().getName() + "号离开");
}, String.valueOf(i)).start();

}

countDownLatch.await();
System.out.println(Thread.currentThread().getName() + "班长离开");

}

AQS之CountDownLatch分析 (八)_实例代码_02

3.源码分析

3.1 构造器

CountDown只有一个构造方法​​public CountDownLatch(int count)​​,用于设置AQS的state,这儿一般称呼为count,即任务数。

public CountDownLatch(int count) {
if (count < 0) throw new IllegalArgumentException("count < 0");
this.sync = new Sync(count);
}

AQS之CountDownLatch分析 (八)_AQS_03

AQS之CountDownLatch分析 (八)_数据结构_04

3.2 获取资源 await

public void await() 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);
}

尝试去获取资源, 如果可以的话, 那么执行获取资源的操作

tryAcquireShared

AQS之CountDownLatch分析 (八)_数据结构_05

判断count是否为0, 为0的话任务全部完成, 不为0返回-1, 任务没有全部完成。这里返回的是1和-1,没有0, 因为成功的时候其他线程申请资源也是成功的。

doAcquireSharedInterruptibly

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) {
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);
}
}

和之前的ReentrantLock 逻辑大致。

3.3 释放资源 countDown

public void countDown() {
sync.releaseShared(1);
}

AQS之CountDownLatch分析 (八)_java_06

尝试释放资源, 如果可以, 执行释放资源的操作

tryReleaseShared

protected boolean tryReleaseShared(int releases) {
// Decrement count; signal when transition to zero
for (;;) {
int c = getState();
if (c == 0)
return false;
int nextc = c-1;
if (compareAndSetState(c, nextc))
return nextc == 0;
}
}

对count进行减1。

doReleaseShared

private void doReleaseShared() {
for (;;) {
Node h = head;
if (h != null && h != tail) {
int ws = h.waitStatus;
if (ws == Node.SIGNAL) {
if (!compareAndSetWaitStatus(h, Node.SIGNAL, 0))
continue; // loop to recheck cases
unparkSuccessor(h);
}
else if (ws == 0 &&
!compareAndSetWaitStatus(h, 0, Node.PROPAGATE))
continue; // loop on failed CAS
}
if (h == head) // loop if head changed
break;
}
}

通过判断waitStatus进行不同的操作, 如果为-1, 通过cas换为0, 然后​​unparkSuccessor​​, 如果为0的话, 重复操作。

线程调用该方法后,计数器的值将递减,如果计数器值为 0, 则唤醒所有因调用 await 方法而被阻塞的线程,否则什么都不做。

3.4 源码总结

当多个线程调动 countDown() 时实际是原子性递减 AQS 的状态值。当调用 await() 后当前线程会被放入 AQS 的阻塞队列等待计数器为0 再返回。

其它线程调用 countDown() 让计数器递减1,当计数器值变为0时,当前线程还要调用 AQS 的 doReleaseShared 来激活由于调用 await() 而被阻塞的线程。


举报

相关推荐

0 条评论