JAVA并发编程--4.3理解CountDownLatch
创始人
2024-03-12 05:29:48

前言:在并发编程过程中我们有时CountDownLatch 来达到异步线程任务执行完毕后,发送通知或进行其它业务的处理,关于 CountDownLatch使用参考:https://blog.csdn.net/l123lgx/article/details/122229867;

CountDownLatch 是怎么工作的:
1 需要new CountDownLatch 来设置state 的值:

  CountDownLatch countDownLatch = new CountDownLatch(1);

CountDownLatch:

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

new Sync(count):

Sync(int count) {setState(count);
}

AbstractQueuedSynchronizer.setState(count):

/**
* The synchronization state.
*/
private volatile int state;
protected final void setState(int newState) {// 将new CountDownLatch(1)的参数1 传入赋值给statestate = newState;}

2 在开启线程处理任务时,在业务处理完毕后:

new Thread(()->{try {// 你的业务处理Thread.sleep(1000);}catch (Exception ex){ex.printStackTrace();}// state 的值-1countDownLatch.countDown();System.out.println("countDown");
}).start();

CountDownLatch.countDown()

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

sync.releaseShared(1):

public final boolean releaseShared(int arg) {if (tryReleaseShared(arg)) {// 如果当前线程在对state -1 后state = 0 则唤醒AQS中阻塞的线程节点doReleaseShared();return true;}return false;
}

CountDownLatch.tryReleaseShared(int releases)

protected boolean tryReleaseShared(int releases) {// Decrement count; signal when transition to zerofor (;;) {// 获取AbstractQueuedSynchronizer state 的值int c = getState();if (c == 0)// c为0 直接返回return false;int nextc = c-1;// 否则将 state 的值-1 if (compareAndSetState(c, nextc))// cas 替换state 的值return nextc == 0;// state 的值-1 为0 则返回true}
}

doReleaseShared():

private void doReleaseShared() {/** Ensure that a release propagates, even if there are other* in-progress acquires/releases.  This proceeds in the usual* way of trying to unparkSuccessor of head if it needs* signal. But if it does not, status is set to PROPAGATE to* ensure that upon release, propagation continues.* Additionally, we must loop in case a new node is added* while we are doing this. Also, unlike other uses of* unparkSuccessor, we need to know if CAS to reset status* fails, if so rechecking.*/for (;;) {Node h = head;// AQS 中的头节点if (h != null && h != tail) {// 如果AQS 中有节点int ws = h.waitStatus;// 获取节点的重入次数if (ws == Node.SIGNAL) {// 如果节点是就绪状态if (!compareAndSetWaitStatus(h, Node.SIGNAL, 0))// 将waitStatus替换为0continue;            // loop to recheck cases 失败则下一次循环unparkSuccessor(h);// 成功则唤醒AQS 的中最早进入的一个节点}else if (ws == 0 &&!compareAndSetWaitStatus(h, 0, Node.PROPAGATE))continue;                // loop on failed CAS}if (h == head)                   // loop if head changedbreak;}}

3 在主线程中通过调用 .await()等待你线程任务的执行完毕:
countDownLatch.await():

countDownLatch.await();public void await() throws InterruptedException {sync.acquireSharedInterruptibly(1);}

AbstractQueuedSynchronizer.acquireSharedInterruptibly(1):

 public final void acquireSharedInterruptibly(int arg)throws InterruptedException {if (Thread.interrupted())throw new InterruptedException();// 线程被中断则直接抛出异常if (tryAcquireShared(arg) < 0)// 如果state 的值不为0 则返回-1 为0返回1doAcquireSharedInterruptibly(arg);}

countDownLatch.tryAcquireShared(int acquires):

protected int tryAcquireShared(int acquires) {// 如果state 的值不为0 则返回-1 为0返回1return (getState() == 0) ? 1 : -1;
}

doAcquireSharedInterruptibly(arg):

/**
* Acquires in shared interruptible mode.* @param arg the acquire argument*/
private void doAcquireSharedInterruptibly(int arg)throws InterruptedException {// 将当前节点加入到AQS 双向链表中,此时初始的 waitStatus 为0final Node node = addWaiter(Node.SHARED);boolean failed = true;try {for (;;) {// 获取当前节点的前置节点final Node p = node.predecessor();if (p == head) {// 如果前置节点为head 节点,也即改node 为AQS 的第一个有效节点int r = tryAcquireShared(arg);// 得到CountDownLatch中的state 的值并判断返回如果state 为0则返回1 否则返回-1if (r >= 0) { //  将当前node 节点设置为AQS的头部节点并唤醒AQS中最早加入的一个节点线程setHeadAndPropagate(node, r);p.next = null; // help GC 移除原头部节点failed = false;return;}}// 设置node 的前置节点的waitStatus 为signal 并挂起当前线程 if (shouldParkAfterFailedAcquire(p, node) &&parkAndCheckInterrupt())throw new InterruptedException();}} finally {if (failed)cancelAcquire(node);// 清除AQS 中的失效节点}
}

setHeadAndPropagate(node, r):

private void setHeadAndPropagate(Node node, int propagate) {Node h = head; // Record old head for check belowsetHead(node);// 设置当前节点为头部节点/** Try to signal next queued node if:*   Propagation was indicated by caller,*     or was recorded (as h.waitStatus either before*     or after setHead) by a previous operation*     (note: this uses sign-check of waitStatus because*      PROPAGATE status may transition to SIGNAL.)* and*   The next node is waiting in shared mode,*     or we don't know, because it appears null** The conservatism in both of these checks may cause* unnecessary wake-ups, but only when there are multiple* racing acquires/releases, so most need signals now or soon* anyway.*/if (propagate > 0 || h == null || h.waitStatus < 0 ||(h = head) == null || h.waitStatus < 0) {Node s = node.next;if (s == null || s.isShared())doReleaseShared();// }
}

AbstractQueuedSynchronizer.setHead(Node node)

private void setHead(Node node) {head = node;node.thread = null;node.prev = null;}

doReleaseShared():

 private void doReleaseShared() {/** Ensure that a release propagates, even if there are other* in-progress acquires/releases.  This proceeds in the usual* way of trying to unparkSuccessor of head if it needs* signal. But if it does not, status is set to PROPAGATE to* ensure that upon release, propagation continues.* Additionally, we must loop in case a new node is added* while we are doing this. Also, unlike other uses of* unparkSuccessor, we need to know if CAS to reset status* fails, if so rechecking.*/for (;;) {Node h = head;// 获取AQS的头部节点if (h != null && h != tail) {// AQS 中有节点int ws = h.waitStatus;if (ws == Node.SIGNAL) {if (!compareAndSetWaitStatus(h, Node.SIGNAL, 0))// 将头部节点的waitStatus 设置为0continue;            // loop to recheck casesunparkSuccessor(h);// 唤醒AQS 中最早加入的一个有效节点线程}else if (ws == 0 &&!compareAndSetWaitStatus(h, 0, Node.PROPAGATE))continue;                // loop on failed CAS}if (h == head)                   // loop if head changedbreak;}
}

unparkSuccessor(h):

private void unparkSuccessor(Node node) {/** If status is negative (i.e., possibly needing signal) try* to clear in anticipation of signalling.  It is OK if this* fails or if status is changed by waiting thread.*/int ws = node.waitStatus;if (ws < 0)compareAndSetWaitStatus(node, ws, 0);/** Thread to unpark is held in successor, which is normally* just the next node.  But if cancelled or apparently null,* traverse backwards from tail to find the actual* non-cancelled successor.*/Node s = node.next;if (s == null || s.waitStatus > 0) {s = null;for (Node t = tail; t != null && t != node; t = t.prev)if (t.waitStatus <= 0)s = t;}if (s != null)LockSupport.unpark(s.thread);
}

4 CountDownLatch 过程:
(1)初始化CountDownLatch时传入要等待定线程个数(int);
(2) 在每个线程中执行完任务后调用countDown()方法对CountDownLatch 的state -1 ;如果减1后state 的值为0,则尝试去唤醒AQS 中处于阻塞的节点;
(3)在主线中调用await() 方法来处理所有线程都执行完任务,如果当前CountDownLatch 的state 不为0 则将当前线程封装为node 加入到AQS双向链表中,随后进行锁的获取,如果获取不到则进行当前线程的挂起;

5 总结:
CountDownLatch 使用state 的变量值来判断线程执行任务的情况,每个线程在执行完成任务后都对改state 减去1 以代表一个任务的执行完毕;适应await() 方法来阻塞等待还没有执行完任务的线程;所以CountDownLatch 的执行 state 值的设置,以及使用countDown()减1 来代表任务的执行完毕,使用await() 方法来阻塞等待还没有执行完任务的线程,3个元素缺一不可;如果使用不当则使得线程阻塞,从而影响业务的执行。

相关内容

热门资讯

埃菲尔铁塔在哪 中国仿建埃菲尔... 2019年4月26日,广西南宁市,街头惊现一座巨型山寨版埃菲尔铁塔,高约20米,白色塔身,造型逼真,...
苗族的传统节日 贵州苗族节日有... 【岜沙苗族芦笙节】岜沙,苗语叫“分送”,距从江县城7.5公里,是世界上最崇拜树木并以树为神的枪手部落...
长白山自助游攻略 吉林长白山游... 昨天介绍了西坡的景点详细请看链接:一个人的旅行,据说能看到长白山天池全凭运气,您的运气如何?今日介绍...
世界上最漂亮的人 世界上最漂亮... 此前在某网上,选出了全球265万颜值姣好的女性。从这些数量庞大的女性群体中,人们投票选出了心目中最美...
猫咪吃了塑料袋怎么办 猫咪误食... 你知道吗?塑料袋放久了会长猫哦!要说猫咪对塑料袋的喜爱程度完完全全可以媲美纸箱家里只要一有塑料袋的响...
应用未安装解决办法 平板应用未... ---IT小技术,每天Get一个小技能!一、前言描述苹果IPad2居然不能安装怎么办?与此IPad不...
脚上的穴位图 脚面经络图对应的... 人体穴位作用图解大全更清晰直观的标注了各个人体穴位的作用,包括头部穴位图、胸部穴位图、背部穴位图、胳...
埃菲尔铁塔在哪 中国仿建埃菲尔... 2019年4月26日,广西南宁市,街头惊现一座巨型山寨版埃菲尔铁塔,高约20米,白色塔身,造型逼真,...
苗族的传统节日 贵州苗族节日有... 【岜沙苗族芦笙节】岜沙,苗语叫“分送”,距从江县城7.5公里,是世界上最崇拜树木并以树为神的枪手部落...
北京的名胜古迹 北京最著名的景... 北京从元代开始,逐渐走上帝国首都的道路,先是成为大辽朝五大首都之一的南京城,随着金灭辽,金代从海陵王...
长白山自助游攻略 吉林长白山游... 昨天介绍了西坡的景点详细请看链接:一个人的旅行,据说能看到长白山天池全凭运气,您的运气如何?今日介绍...
猫咪吃了塑料袋怎么办 猫咪误食... 你知道吗?塑料袋放久了会长猫哦!要说猫咪对塑料袋的喜爱程度完完全全可以媲美纸箱家里只要一有塑料袋的响...
应用未安装解决办法 平板应用未... ---IT小技术,每天Get一个小技能!一、前言描述苹果IPad2居然不能安装怎么办?与此IPad不...
脚上的穴位图 脚面经络图对应的... 人体穴位作用图解大全更清晰直观的标注了各个人体穴位的作用,包括头部穴位图、胸部穴位图、背部穴位图、胳...