56星座屋
当前位置: 首页 星座百科

jdk并发包使用教程(线程并发协调神器CountDownLatch和CyclicBarrier)

时间:2023-07-17 作者: 小编 阅读量: 1 栏目名: 星座百科

就拿CountDownLatch来说,它的命名形象的表示了其能力属性,Count代表着计数,Down代表着计数器的递减操作,而Latch表示计数器递减后的结果动作。当计数器置为0后,阻塞的线程被释放。使用场景就像上文描述的,CountDownLatch就像是田径赛场上裁判员发射的发令枪,所有参赛的选手准备就绪后,发令枪一响,所有运动员闻声而动。这就好比公司组织团建,约定好了早上8点半在公司大门集合,那么司机师傅肯定要等到所有参加团建的同时都到齐后才会发车。

jdk并发包使用教程?我一直认为程序是对于现实世界的逻辑描述,而在现实世界中很多事情都需要各方协调合作才能完成,就好比完成一个平台的交付不可能只靠一个人,而需要研发、测试、产品以及项目经理等不同角色人员进行通力合作才能完成最终的交付那么在程序的世界中是如何对这种协调关系进行描述的呢?今天就和大家聊聊Java大神Doug Lea在并发包中如何通过CountDownLatch和CyclicBarrier实现任务协调的代码描述,今天小编就来说说关于jdk并发包使用教程?下面更多详细答案一起来看看吧!

jdk并发包使用教程

引言

我一直认为程序是对于现实世界的逻辑描述,而在现实世界中很多事情都需要各方协调合作才能完成,就好比完成一个平台的交付不可能只靠一个人,而需要研发、测试、产品以及项目经理等不同角色人员进行通力合作才能完成最终的交付。那么在程序的世界中是如何对这种协调关系进行描述的呢?今天就和大家聊聊Java大神Doug Lea在并发包中如何通过CountDownLatch和CyclicBarrier实现任务协调的代码描述。

CountDownLatch

我相信大家都知道好代码的一个重要特性就是代码中类、变量等的命名可以做到顾名思义,也就是说看到命名就可以大概知道这个类或者变量表达了怎样的业务语义。就拿 CountDownLatch 来说,它的命名形象的表示了其能力属性,Count代表着计数,Down代表着计数器的递减操作,而Latch表示计数器递减后的结果动作。CountDownLatch结合起来的字面意思就是计数器递减后打开门栓,通过后面内容的描述,回过头来看大家肯定会觉得这个命名十分之形象。

好了通过它的类的名称,我们猜测了它的功能是通过计数器的递减操作来控制线程,那我们再看看官方描述是不是这个意思。

/** * A synchronization aid that allows one or more threads to wait until * a set of operations being performed in other threads completes. * * <p>A {@code CountDownLatch} is initialized with a given <em>count</em>. * The {@link #await await} methods block until the current count reaches * zero due to invocations of the {@link #countDown} method, after which * all waiting threads are released and any subsequent invocations of * {@link #await await} return immediately.This is a one-shot phenomenon * -- the count cannot be reset.If you need a version that resets the * count, consider using a {@link CyclicBarrier}. *...*/

上面注释的大致意思就是CountDownLatch是一个线程同步器,它允许一个或者多个线程阻塞等待直到其他线程中业务执行完成。CountDownLatch可以通过一个计数器进行初始化,他可以让那个等待的线程被阻塞,直到对应的计数器被置为0。当计数器置为0后,阻塞的线程被释放。另外它是一个一次性使用的同步器,计数器无法被重置。

通过JDK的官方描述我们可以明确CountDownLatch三个核心特征:

1、它是一种线程同步器,用以协调线程的执行触发时机;

2、它本质是一个计数器,是控制线程的号令枪;

3、它是一次性使用的,用完即失效。

知道了CountDownLatch是一个什么东东之后,我们再一起来看下它的使用场景是什么,我们在什么样的情况下可以使用它帮我们解决一些代码中的问题。

使用场景

就像上文描述的,CountDownLatch就像是田径赛场上裁判员发射的发令枪,所有参赛的选手准备就绪后,发令枪一响,所有运动员闻声而动。那么在Java多线程场景中,CountDownLatch就是线程协调者,它的计数器在没有减为0之前。假设有这样一个业务场景,在一个监控告警平台中,需要从告警服务中查询告警信息以及从工单服务中查询工单信息,然后再分析哪些告警没有转工单。按照老系统的做法,参见如下简化后的伪代码:

List<Alarm> alarmList = alarmService.getAlarm();List<WorkOrder> workOrderList = workOrderService.getWorkOrder();List<Alarm> notTransferToWorkOrder = analysis(alarmList, workOrderList);

大家能看出来这段伪代码有什么需要进行优化的地方吗?我们来一起分析一下。这段代码在数据量不大的时候可能没什么影响,但是一旦告警以及工单的数据量大的时候,获取告警信息或者获取工单信息都可能出现数据查询慢的问题,那就会导致这个分析任务就会出现性能瓶颈的问题。那么我们应该怎么进行优化呢?从业务以及代码我们可以看的出来,获取告警信息以及获取工单信息,实际上并没有业务上面的耦合性,在上述代码中他们是顺序执行的,因此要进行性能优化,可以考虑将它们进行并行执行。

那么修改优化后的伪代码如下所示:

Executor executor = Executors.newFixedThreadPool(2);executor.execute(()-> { alarmList = alarmService.getAlarm(); });executor.execute(()-> { workOrderList = workOrderService.getWorkOrder(); }); List<Alarm> notTransferToWorkOrder = analysis(alarmList, workOrderList);

我们通过使用线程池的方式,在获取告警信息以及工单信息的时候并发执行,不再像之前的执行完获取告警信息再执行获取工单信息,这样效率更高。但是这样的实现方式还是存在问题,由于在线的线程中执行操作,并不知道其实际的执行结果,这就不好判断执行数据分析的具体时机。这个时候CountDownLatch就派上用场了,利用它可以实现线程拣的等待,条件满足后再放开执行后续的逻辑。这就好比公司组织团建,约定好了早上8点半在公司大门集合,那么司机师傅肯定要等到所有参加团建的同时都到齐后才会发车。

使用CountDownLatch之后的伪代码如下所示:

Executor executor = Executors.newFixedThreadPool(2);CountDownLatch latch = new CountDownLatch(2);executor.execute(()-> { alarmList = alarmService.getAlarm();latch.countDown();});executor.execute(()-> { workOrderList = workOrderService.getWorkOrder();latch.countDown();});latch.await();List<Alarm> notTransferToWorkOrder = analysis(alarmList, workOrderList);

底层实现原理初始化

在使用CountDownLatch之前我们得先进行初始化,在初始化的过程中实际做了两件事情,一个是创建了一个AQS的同步队列,另外一个是将AQS中的state设置成了count,这个state是AQS的核心变量(AQS是并发包的底层实现基础,关于它的分析我们放到下一篇文章中进行)。

从代码中我们可以看的出来实际创建了Sync内部类实例,而Sync继承了AQS,同时重写了AQS加锁解锁的方法,并通过Sync的对象,调用AQS的方法,阻塞线程的运行。Sync内部类的代码如下所示,其中tryAcquireShared方法重写了AQS的模板方法,主要用来获取共享锁,在CountDownLatch内部主要通过判断获取到的state的值是否为0来决定到底有没有获取到锁。如果获取到的state为0,则表示获取锁成功,此时线程不会阻塞,反之则获取锁失败,线程会阻塞。

private static final class Sync extends AbstractQueuedSynchronizer {private static final long serialVersionUID = 4982264981922014374L;Sync(int count) {setState(count);}int getCount() {return getState();}//尝试加共享锁(通过state判断)protected int tryAcquireShared(int acquires) {return (getState() == 0) ? 1 : -1;}//尝试释放共享锁(通过state判断)protected boolean tryReleaseShared(int releases) {// Decrement count; signal when transition to zerofor (;;) {int c = getState();if (c == 0)return false;int nextc = c-1;if (compareAndSetState(c, nextc))return nextc == 0;}}}

计数器递减

如上文场景中介绍的代码,每个线程在执行完成自身业务后执行countDown操作,表示该线程已经准备完成。同时检查count值是否为0。如果为0则需要唤醒所有等待的线程。如下代码所示,实际上它调用的是父类AQS的releaseShared方法。

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

tryReleaseShared这个方法实际是进行尝试释放锁的操作,如果此次count递减为0,然后释放所有的线程。

public final boolean releaseShared(int arg) {if (tryReleaseShared(arg)) {doReleaseShared();return true;}return false;}

大致的代码执行逻辑可参见下图:

阻塞线程

await的作用就是将当前线程阻塞住,直到count值减为0才会放开执行。它实际调用了内部类的tryAcquireSharedNanos方法,这个方法实际是Sync类的父类AQS中的方法。

public void await() throws InterruptedException {sync.acquireSharedInterruptibly(1);}复制代码

AQS提供了可以响应中断的获取公平锁的实现的方式。tryAcquireShared在上文已经进行了介绍,该方法的作用是尝试获取共享锁,如果获取失败,则线程将会被加入到AQS的同步队列中进行等待,也就是所谓的线程阻塞。

public final void acquireSharedInterruptibly(int arg)throws InterruptedException {if (Thread.interrupted())throw new InterruptedException();if (tryAcquireShared(arg) < 0)doAcquireSharedInterruptibly(arg);}

CyclicBarrier

我们还是从CyclicBarrier的字面意思来先进行理解,Cyclic是循环的意思而Barrier则表示栅栏、障碍的意思,字面的意思就是可循环的栅栏。还是老套路,在进行CyclicBarrier之前,我们先来看下JDK是怎么描述的。

/** * A synchronization aid that allows a set of threads to all wait for * each other to reach a common barrier point.CyclicBarriers are * useful in programs involving a fixed sized party of threads that * must occasionally wait for each other. The barrier is called * <em>cyclic</em> because it can be re-used after the waiting threads * are released. * * <p>A {@code CyclicBarrier} supports an optional {@link Runnable} command * that is run once per barrier point, after the last thread in the party * arrives, but before any threads are released. * This <em>barrier action</em> is useful * for updating shared-state before any of the parties continue. *... **/

通过JDK的描述,我们可以看得出来,CyclicBarrier也是一个线程同步协调器,用以协调一组进程的执行。当指定个数的线程到达栅栏后,可以放开栅栏,结束线程阻塞状态。这么看上去它和CountDownLatch作用差不多了,实际上还是有区别的,CyclicBarrier是可循环使用的,而CountDownLatch却是一次性的。我们来看下CyclicBarrier的核心属性。

//栅栏入口的锁private final ReentrantLock lock = new ReentrantLock();//线程等待条件private final Condition trip = lock.newCondition();//拦截的线程数量private final int parties;//在下一个栅栏代数到来前执行的任务private final Runnable barrierCommand;//当前的栅栏代数private Generation generation = new Generation();

CyclicBarrier 的源码实现和 CountDownLatch 大同小异,CountDownLatch 基于 AQS 的共享模式的使用,而 CyclicBarrier 基于 Condition 来实现的。

CyclicBarrier内部维护了parties和count变量,parties表示每次参与到一个Generation中需要被拦截的线程数量,而count是内部计数器,在初始化的时候count与parties相等,当每次调用await方法的时候计数器count就会减1,这和上文中的countDown类似。

使用场景

还是以上文中的业务场景为例我们再分析一下,上文中我们通过CountDownLatch实现了查询告警信息与查询工单信息的线程协调问题,但是新的问题又出现了。因为告警信息和工单信息都是实时在产生的,而使用CountDownLatch的实现方式只能完成一次的线程协调,后续产生的告警信息以及工单信息如果还有需要查询到之后再进行数据分析的话,它就爱莫能助了。也就是说,如果需要进行持续的线程之间的互相等待完成之后再执行后续的业务操作的话,这个时候就需要使用CyclicBarrier 来实现我们的需求了。

底层实现原理初始化

CyclicBarrier 存在两种的构造函数,一种是构建CyclicBarrier 的时候指定每次需要进行协调的线程个数以及解除阻塞之后需要进行后续任务的执行,另一种只是设置需要协调的线程个数不设置后续执行的任务。

public CyclicBarrier(int parties, Runnable barrierAction) {if (parties <= 0) throw new IllegalArgumentException();this.parties = parties;this.count = parties;this.barrierCommand = barrierAction;}public CyclicBarrier(int parties) {this(parties, null);}

阻塞等待

对于CyclicBarrier 来说,其最核心的等待方法实现就是dowait方法,具体代码如下所示:

private int dowait(boolean timed, long nanos)throws InterruptedException, BrokenBarrierException,TimeoutException {final ReentrantLock lock = this.lock;lock.lock();try {final Generation g = generation;if (g.broken)throw new BrokenBarrierException();if (Thread.interrupted()) {breakBarrier();throw new InterruptedException();}int index = --count;//如果count计算为0,则需要唤醒所有线程并进入到下一阶段的线程协调期if (index == 0) {// trippedboolean ranAction = false;try {final Runnable command = barrierCommand;if (command != null)command.run();ranAction = true;nextGeneration();return 0;} finally {if (!ranAction)breakBarrier();}}//计数器不为0,继续进行循环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 {// We're about to finish waiting even if we had not// been interrupted, so this interrupt is deemed to// "belong" to subsequent execution.Thread.currentThread().interrupt();}}if (g.broken)throw new BrokenBarrierException();if (g != generation)return index;if (timed && nanos <= 0L) {breakBarrier();throw new TimeoutException();}}} finally {lock.unlock();}}

我们可以看到在dowait方法中进行了count的递减操作,检查count的值是否为0,如果在初始化的时候定义好了要执行的任务,那么在count为0的时候就进行任务执行,任务执行完成之后调用nextGeneration进行下一次的线程协调周期,同时唤醒所有线程并重置计数器。

总结

本文分别从使用场景以及底层实现的角度分别介绍了线程同步协调神器CountDownLatch和CyclicBarrier,虽然它们都可以起到协调线程的作用但是实际上它们还是有区别的。CountDownLatch比较适合一个线程与其他多个线程之间的同步协调场景,而CyclicBarrier则适合一组线程之间的互相等待。另外CountDownLatch是一次性产品,而CyclicBarrier的计数器是可以重复使用的,可以进行自动重置计数器。

作者:慕枫技术笔记链接:http://juejin.cn/post/7068579732161298446

    推荐阅读
  • 手抓饭的做法(怎么制作手抓饭)

    以下内容希望对你有帮助!手抓饭的做法食材:羊排、胡萝卜、洋葱、大米、山楂、葡萄干、花椒、孜然粉、食用油。大米用水泡半个小时,羊肉切小块,胡萝卜切丁或丝,洋葱切丁。锅里放油,放入洋葱炒出香味,再放入羊肉翻炒。倒水没过羊肉煮大约十分钟。把所有炒好的菜,肉,汤和葡萄干,全部倒入电饭锅,把泡好的米饭均匀撒在上面一层,1:2比例为好,开始焖20分钟。打开后,菜,肉,米饭拌匀即可食用。

  • jdk并发包使用教程(线程并发协调神器CountDownLatch和CyclicBarrier)

    就拿CountDownLatch来说,它的命名形象的表示了其能力属性,Count代表着计数,Down代表着计数器的递减操作,而Latch表示计数器递减后的结果动作。当计数器置为0后,阻塞的线程被释放。使用场景就像上文描述的,CountDownLatch就像是田径赛场上裁判员发射的发令枪,所有参赛的选手准备就绪后,发令枪一响,所有运动员闻声而动。这就好比公司组织团建,约定好了早上8点半在公司大门集合,那么司机师傅肯定要等到所有参加团建的同时都到齐后才会发车。

  • window10玩dnf卡顿(要怎么解决呢)

    接下来我们就一起去了解一下吧!window10玩dnf卡顿首先使用鼠标在桌面空白右击,并在弹出菜单中使用鼠标左击项目。在点击左侧的设项目,之后使用鼠标左击下方的项目,在弹出的菜单中使用鼠标左击项目。接着在设置列表中找到,使用鼠标左击窗口右侧的项目,在弹出的菜单中使用鼠标左击项目。设置完成后点击应用,保存设置即可。

  • 排球小组赛积分规则(排球赛小组积分制是怎么积分得出结果的)

    我们一起去了解并探讨一下这个问题吧!排球小组赛积分规则小组赛都是按照胜场计算,胜者加分。前4局比赛采用25分制,每个队只有赢得至少25分,并同时超过对方2分时,才胜1局。正式比赛采用5局3胜制,决胜局的比赛采用15分制,一队先得8分后,两队交换场区,按原位置顺序继续比赛到结束。在决胜局(第五局)的比赛,先获15分并领先对队2分为胜。

  • 敷毛孔收敛水的正确步骤(怎么敷毛孔收敛水)

    所以,第一步就是要清洗双手,避免脏污蔓延到脸上的毛孔里。卸掉面部彩妆,洗面奶清洗脸部,或用一些洗脸仪.洗完脸之后,姑娘们就可以开始用清洁面膜或者去黑头的护肤品了,更加安全。如果姑娘们急着出门,那么用完城收敛水之后做好保湿,使用质地清爽的面霜也是没问题的。无论何时,为了我们肌肤的年轻状态,使用保湿型面霜和乳液都是不可缺少的哦。

  • 沉香如屑杨紫造型不如女二(沉香如屑的3大争议)

    《沉香如屑》空降播出,网友吐槽剧情俗套,杨紫颜值不如孟子义——引言。很多网友喜欢杨紫,主要还是看实力,奈何《沉香如屑》里面,由于她与孟子义的同框画面有所差距,使得杨紫被无数网友吐槽脸垮了。随后,孟子义艳压杨紫的词条便登上了热搜。不仅女演员被拿来对比,成毅与张睿也同样被网友吐槽。因此,《沉香如屑》的前期预热有多火,如今其不如期待值的吐槽声就有多激烈。

  • 喜出望外望的意思(喜出望外解释)

    下面希望有你要的答案,我们一起来看看吧!喜出望外望的意思喜出望外的望释义:希望,意料。喜出望外,汉语成语,拼音是xǐchūwàngwài,释义:是指因遇到意外的喜事,心中非常高兴。出自宋·苏轼《与李之仪》。成语辨析近义词:大喜过望、喜从天降、欣喜若狂、眉开眼笑、如获至宝。

  • 超过30岁的女人必备护肤品(这3种功能性护肤品要常用)

    而且上了年纪之后,眼睛周围的皮肤也需要补水,这样才能做到提亮眼周皮肤,预防新的皱纹产生。随着年龄的增长,皮肤毛孔会变大,里面就会积攒更多的粉尘和垃圾,如果不及时的清理,就会引发闭口,肤色暗沉,角质层增厚等问题。不过清洁面膜也不宜多做,一周1次就够了,频繁的使用清洁面膜很容易破坏皮肤屏障,让皮肤变成敏感肌。另外在使用完清洁面膜之后一定要做好皮肤的补水工作,因为清洁面膜在清洁的同时还会带走一定的水分。

  • 家具定制安装,常见问题解决方法 定制家具出现问题如何处理

    定制家具安装往往存在各种问题,因此解决方案是主人需要理解和学习,勤学知装修网www.qinxuezhi.com帮助主人了解常见问题的解决方案。电路板没有边缘密封,因此这需要端子设计来考虑间隙。该板应平整,固定,并用钻头正确钻孔,钻头必须垂直!滑动门的一般问题相对较小,相对来说,一扇门被推到右边。右边有一个空隙,这是一个推拉门或门洞本身不垂直。

  • 春节幸福的祝福语(春节来临之际的祝福)

    春节幸福的祝福语事业龙飞凤舞,家庭龙凤呈祥,儿孙龙驹凤雏,健康龙马精神,思维龙腾虎跃,新的一年里愿朋友龙兴风聚,大有作为,万事龙章凤彩,福寿安康!春节人多伤脑筋,满城内外都拥挤,鞭炮烟花要远离,老人小孩少出行,睡眠规律人有劲,春节怎能不开心!春节祝福短信来到,祝你在新年里:事业如日中天,心情阳光灿烂,工资地覆天翻,未来风光无限,爱情浪漫依然,愉快游戏人间。