Android 并发编程—CountDownLatch

1.CountDownLatch
CountDownLatch是一个多线程同步工具类,它允许一个或多个线程一直等待,直到其他线程运行完成后,等待线程再继续向下执行。
CountDownLatch内部有一个计数器和一个阻塞队列,每当某个线程调用一次countDown()方法,计数器的值就减1。当计数器的值不为0时,调用await()方法的线程就会被加入到阻塞队列,一直阻塞到计数器的值为0时调用await()方法的线程才可以继续向下执行。

常用方法:
①构造一个值为count的计数器
public CountDownLatch(int count);
②阻塞当前线程直到计数器为0
public void await() throws InterruptedException;
③在单位为unit的timeout时间之内阻塞当前线程
public boolean await(long timeout, TimeUnit unit);
④计数器减1
public void countDown();

注:
①CountDownLatch构造函数中的count就是闭锁需要等待的线程数量。这个值只能被设置一次,且不能重新设置。也就是说CountDownLatch是不能够重用的,如果需要重新计数,需要再定义一个新的CountDownLatch。
②调用await()方法的线程会被阻塞,直到构造方法传入的N减到0时,被阻塞线程才能继续往下执行。

CountDownLatch的两种使用场景:
场景1:让多个线程等待
场景2:让单个线程等待
①场景1 让多个线程等待:模拟并发,让并发线程一起执行
为了模拟高并发,让一组线程在指定时刻同时执行,这些线程在准备就绪后,进行await()等待,直到指定时刻(N变为0)到来,然后同时执行。
代码实现:
CountDownLatch countDownLatch = new CountDownLatch(1);
for (int i = 0; i < 5; i++) {
new Thread(() -> {
try {
countDownLatch.await(); //阻塞等待
Log.e(TAG, Thread.currentThread().ge tName()+ “开始执行……”);
} catch (InterruptedException e) {
e.printStackTrace();
}
}).start();
}
Thread.sleep(2000);// 主线程做自己的事情
countDownLatch.countDown();//此时n变为0,等待结束

运行结果:
【Thread-0】开始执行……
【Thread-1】开始执行……
【Thread-4】开始执行……
【Thread-3】开始执行……
【Thread-2】开始执行……

②场景2 让单个线程等待:多个线程(任务)完成后,进行汇总合并
很多时候并发任务存在前后依赖关系,比如数据详情页需要同时调用多个接口获取数据,并发请求获取到数据后,需要进行结果合并;或者多个数据操作完成后,需要数据check……这其实都是:在多个线程完成后,进行汇总合并的场景。
代码实现:
CountDownLatch countDownLatch = new CountDownLatch(5);
for (int i = 0; i < 5; i++) {
final int index = i;
new Thread(() -> {
try {
Thread.sleep(1000 + ThreadLocalRandom.current().nextInt(1000));
Log.e(TAG, “finish” + index + Thread.currentThread().getName());
countDownLatch.countDown();
} catch (InterruptedException e) {
e.printStackTrace();
}
}).start();
}
countDownLatch.await();// 主线程在阻塞,当计数器变为0时就唤醒主线程往下执行
Log.e(TAG, “主线程:在所有任务运行完成后,进行结果汇总”);

运行结果:
finish4Thread-4
finish1Thread-1
finish2Thread-2
finish3Thread-3
finish0Thread-0
主线程:在所有任务运行完成后,进行结果汇总

在每个线程完成的最后一行加上countDownLatch.countDown()让计数器减1,当所有线程完成减1,计数器减到0后,主线程往下执行汇总任务。

2.CountDownLatch原理
CountDownLatch是基于AQS共享模式的使用。
通过CountDownLatch的构造函数给AQS的state状态变量赋值。
public class CountDownLatch {
private final Sync sync; //Sync继承自AQS

public CountDownLatch(int count) {
if (count < 0) throw new IllegalArgumentException(“count < 0”);
this.sync = new Sync(count);
}
public void await() {
sync.acquireSharedInterruptibly(1);
}
public void countDown() {
//调用AQS中的releaseShared(1)释放共享锁方法
sync.releaseShared(1);
}

private static final class Sync extends AbstractQueuedSynchronizer {
Sync(int count) {
setState(count);//计数器的值赋给了AQS状态变量state
}
int getCount() {
return getState();
}
//尝试获取共享锁,重写AQS里面的方法
protected int tryAcquireShared(int acquires) {
//锁状态==0表示锁空闲,是可获取的状态,否则锁是不可获取状态
return (getState() 0) ? 1 : -1;
}
protected boolean tryReleaseShared(int releases) {
//自旋,直到当前线程完成CAS减去1操作
for ( ; ; ) {
int c = getState();
//当前状态值为0则直接返回
if (c
0)
return false;
int nextc = c-1;
//使用CAS让计数器值减去1
if (compareAndSetState(c, nextc))
return nextc ==0;
}
}
}
}

3.await()方法解析
CountDownLatch中await()方法实际上调用了sync的acquireSharedInterruptibly()方法:
public void await() {
sync.acquireSharedInterruptibly(1);
}
acquireSharedInterruptibly用于获取共享锁,具体实现在AQS里:
public abstract class AbstractQueuedSynchronizer extends AbstractOwnableSynchronizer {
public final void acquireSharedInterruptibly( int arg) throws InterruptedException {
//线程为中断状态,则抛出异常
if (Thread.interrupted())
throw new InterruptedException();
//尝试获取共享锁,尝试成功就返回,否则调用doAcquireSharedInterruptibly方法
if (tryAcquireShared(arg) < 0)
doAcquireSharedInterruptibly(arg);
}
}
在CountDownLatch中重写了tryAcquireShared方法:
protected int tryAcquireShared(int acquires) {
//锁状态等于0表示锁空闲,是可获取的状态,否则锁是不可获取状态
return (getState() = = 0) ? 1 : -1;
}
当count不等于0时(即获取不到锁)会调用doAcquireSharedInterruptibly()方法,该方法会使当前线程一直等待,直到当前线程获取到锁(或被中断)才返回,具体实现在AQS里:
public abstract class AbstractQueuedSynchronizer extends AbstractOwnableSynchronizer {
private void doAcquireSharedInterruptibly( int arg) throws InterruptedException {
//将当前线程包装为Node节点,Node中使用共享锁类型,并将该节点添加到CLH队列末尾
final Node node = addWaiter( Node.SHARED);
try {
for (;😉 {
final Node p = node.predecessor();//获取前驱节点
if (p = = head) { //前驱节点是等待锁队列的表头,则尝试获取共享锁(即判断新增节点的前一个节点是否为头节点)
int r = tryAcquireShared(arg);
if (r >= 0) { // 获取锁成功
setHeadAndPropagate(node, r); //把当前节点变为新的head节点,并检查后继节点是否可以在共享模式下等待并且允许继续传播,然后调用doReleaseShared继续唤醒下一个节点尝试获取锁
p.next = null; // help GC
return;
}
}
//前驱节点不是头节点,当前线程一直等待,直到获取到锁
if (shouldParkAfterFailedAcquire(p, node) && parkAndCheckInterrupt())
throw new InterruptedException();
}
} catch(Throwable t) {
cancelAcquire(node);
throw t;
}
}
}
当前驱节点不是头节点时,会调用shouldParkAfterFailedAcquire方法让当前线程一直等待,直到获取到锁。
shouldParkAfterFailedAcquire方法用于判断当前线程获取锁失败之后是否需要挂起,具体实现在AQS里:
public abstract class AbstractQueuedSynchronizer extends AbstractOwnableSynchronizer {
private static boolean shouldParkAfterFailedAcquire(Node pred, Node node) {
int ws = pred.waitStatus; //前驱节点的状态
// 如果前驱节点是SIGNAL状态,则意味着当前线程需要unpark唤醒,此时返回true
if (ws== Node.SIGNAL)
return true;
// 如果前继节点是取消状态CANCELLED
if (ws > 0) {
// 从队尾向前寻找第一个状态不为CANCELLED的节点
do {
node.prev = pred = pred.prev;
} while (pred.waitStatus > 0);
pred.next = node;
} else {
// 将前驱节点的状态设置为SIGNAL
compareAndSetWaitStatus(pred, ws, Node.SIGNAL);
}
return false;
}
}
await()方法本质是获取共享锁,核心实现是:getState()0,即如果state0,则获取成功,否则线程阻塞进入等待队列。

4.countDown()方法解析
CountDownLatch的countDown()方法实际上调用的是sync的releaseShared来释放共享锁:
public void countDown() {
sync.releaseShared(1);
}
releaseShared()方法目的是让当前线程释放它所持有的共享锁,它首先会通过tryReleaseShared()去尝试释放共享锁,尝试成功则直接返回,尝试失败则通过doReleaseShared()去释放共享锁。具体实现在AQS里:
public abstract class AbstractQueuedSynchronizer extends AbstractOwnableSynchronizer {
public final boolean releaseShared(int arg) {
if (tryReleaseShared(arg)) {
doReleaseShared();
return true;
}
return false;
}
}
CountDownLatch中重写了tryReleaseShared()方法:
public class CountDownLatch {
private final Sync sync;
private static final class Sync extends AbstractQueuedSynchronizer {
protected boolean tryReleaseShared(int releases) {
for (;😉 {
int c = getState();// 锁计数器的状态
if (c==0)
return false;
int nextc = c-1;// “锁计数器”-1
// 通过CAS函数进行赋值
if (compareAndSetState(c, nextc))
return nextc == 0;
}
}
}
}
doReleaseShared()方法的具体实现在AQS里:
public abstract class AbstractQueuedSynchronizer extends AbstractOwnableSynchronizer {
private void doReleaseShared() {
for(;😉 {
Node h = head;
if(h != null && h != tail) {
int ws = h.waitStatus;
if(ws = = Node.SIGNAL) {
if(!h.compareAndSetWaitStatus( Node.SIGNAL, 0)
continue;
unparkSuccessor(h);
} else if(ws = = 0 && !h.compareAndSetWaitStatus(0, Node.PROPAGATE)
continue;
}
if(h = = head)
break;
}
}
}
countDown()方法本质是释放共享锁,核心实现逻辑是:state>0 && state-1,即如果state>0,则state减一,否则执行失败。

当state减到0的时候会唤醒等待队列中的所有线程,尝试继续获取共享锁,这个时候正常是所有线程都能获取成功的。

5.CountDownLatch与Thread.join
CountDownLatch的作用就是允许一个或多个线程等待其他线程完成操作,看起来有点类似join()方法,但其提供了比join()更加灵活的API。
CountDownLatch可以手动控制在n个线程里调用n次countDown()方法使计数器进行减一操作,也可以在一个线程里调用n次执行减一操作。
而join()的实现原理是不停检查join线程是否存活,如果join线程存活则让当前线程永远等待。所以两者之间相对来说还是CountDownLatch使用起来较为灵活。

CountDownLatch与CyclicBarrier:
CountDownLatch和CyclicBarrier都能够实现线程之间的等待,只不过它们侧重点不同:
CountDownLatch一般用于一个或多个线程等待其他线程执行完任务后才执行;
CyclicBarrier一般用于一组线程互相等待至某个状态,然后这一组线程再同时执行;
另外,CountDownLatch是减计数,计数减为0后不能重用;而CyclicBarrier是加计数,可置0后复用。

  • 0
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
CountDownLatch是Java并发编程中的一种同步工具类。它的作用是允许一个或多个线程等待其他线程完成操作。CountDownLatch的构造函数接收一个计数值,当计数值变为0时,等待的线程就会被唤醒。使用CountDownLatch可以实现多线程并发编程中的线程协作。 与使用join方法等待多个线程执行完毕不同,CountDownLatch相对灵活。可以通过调用countDown方法来减少计数,唤醒被阻塞的线程。这使得多个线程可以同时进行,并在一定条件下等待其他线程完成后再继续执行。 CountDownLatch一般称为闭锁或计数器,它是Java并发编程中的一种多线程同步工具。它属于AQS(AbstractQueuedSynchronizer)体系的一员,可以实现线程之间的协作和同步操作。<span class="em">1</span><span class="em">2</span><span class="em">3</span> #### 引用[.reference_title] - *1* [Java多线程之并发工具类](https://download.csdn.net/download/weixin_38713057/13756829)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v93^chatsearchT3_2"}}] [.reference_item style="max-width: 33.333333333333336%"] - *2* [java多线程并发之CountDownLatch](https://blog.csdn.net/weixin_42419762/article/details/116220340)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v93^chatsearchT3_2"}}] [.reference_item style="max-width: 33.333333333333336%"] - *3* [多线程编程之 CountDownLatch](https://blog.csdn.net/jiangxiayouyu/article/details/118107977)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v93^chatsearchT3_2"}}] [.reference_item style="max-width: 33.333333333333336%"] [ .reference_list ]

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值