当前位置: 首页 > 科技观察

面试过十几家公司,每次问的方式都不一样,CyclicBarrier的详解

时间:2023-03-22 12:07:08 科技观察

之前介绍过java中另外一个同步工具类,CountDownLatch。本文主要介绍CyclicBarrier。重要性不用多说,面试的时候,面试官绕着走,针对特定的场景提问,最后提醒他们回答。所以如果用在面试中,一定要结合很多场景来回答。1.概念理解CyclicBarrier允许一组线程在到达共同的障碍点时相互等待。直到最后一个线程到达公共屏障点才会打开屏障,被阻塞的线程将恢复执行。比如我们玩王者的时候,十个玩家必须全部加载到100%才能开始游戏。否则只要一个人没有加载到100%,游戏就无法开始。第一个加载的玩家必须等待最后一个玩家加载成功。如果实在想不起来,可以想象一下坐满大人的长途汽车。即使你是第一个上车的人,也得等到车满了才能发车。不然的话,车上的人都得等。与CountDownLatch的区别在于是否相互等待。举个例子,CountDownLatch就像一场马拉松。跑完的人不用等其他玩家跑完,而CyclicBarrier需要等最后一个玩家加载完。这就是区别。我们直接演示这个例子。2.代码演示这里同样以打王者为例。首先我们定义主线程:publicclassCyclicBarrierTest{publicstaticvoidmain(String[]args){//第一步:定义播放器,这里写5个String[]heros={"孙悟空","猪八戒","狄仁杰"","鲁班","甄姬"};//第二步:使用线程池运行,也是5.ExecutorServiceservice=Executors.newFixedThreadPool(5);//第3步:普通fences,也是5个finalCyclicBarrierbarrier=newCyclicBarrier(10);//第4步:通过for循环传递给每个player和fencefor(inti=0;i<10;i++){service.execute(newPlayer(heros[i],barrier));}service.shutdown();}}上面的代码我们已经解释清楚了,主要是通过线程池运行播放器,并通过给每个播放器一个名称和围栏。我们来看看这个Player播放器线程是如何实现的。publicclassPlayerimplementsRunnable{privatefinalStringhero;privatefinalCyclicBarrierbarrier;publicPlayer(Stringhero,CyclicBarrierbarrier){this.hero=hero;this.barrier=barrier;}@Overridepublicvoidrun(){try{//每个英雄加载成功的时间不一样,所以使用random这里NumberTimeUnit.SECONDS.sleep(1+(newRandom().nextInt(5)));System.out.println(hero+"开始加载==========等待其他玩家加载成功");barrier.await();System.out.println(hero+":看到所有玩家加载成功,游戏开始");}catch(InterruptedException|BrokenBarrierExceptione){e.printStackTrace();}}}在这个玩家线程中,我们使用随机数代表每个玩家的不同加载时间。在休眠时间结束之前,播放器处于等待状态,即调用了await方法。现在测试一下。现在相信通过这个案例,大家可以掌握它的用法了,非常简单。下面我们从源码的角度来分析一下它的实现原理。3.源码分析为了分析透彻,我们先从构造方法入手://构造方法一:parties主要是需要拦截的线程数publicCyclicBarrier(intparties){this(parties,null);}//构造方法2:不仅有parties还有barrierAction//主要是处理比较复杂的场景,当线程到达fence时//优先执行barrierActionpublicCyclicBarrier(intparties,RunnablebarrierAction){if(parties<=0)thrownewIllegalArgumentException();this.parties=parties;this.count=parties;this.barrierCommand=barrierAction;}以上是两种构造方法。下面我们主要分析await方法。我们进入源码看看://withouttimeoutpublicintawait()throwsInterruptedException,BrokenBarrierException{try{returndowait(false,0L);}catch(TimeoutExceptiontoe){thrownewError(toe);//cannothappen}}//withtimeoutpublicintawait(longtimeout,TimeUnitunit)throwsInterruptedException,BrokenBarrierException,TimeoutException{returndowait(true,unit.toNanos(timeout));}返回值为加载成功的播放器数。由于内部实现是通过dowait方法实现的,所以我们跟进看看。privateintdowait(booleantitimed,longnanos)throwsInterruptedException,BrokenBarrierException,TimeoutException{finalReentrantLocklock=this.lock;lock.lock();try{finalGenerationg=generation;if(g.broken)thrownewBrokenBarrierException();if(Thread.interrupted()){breakBarrier();thrownewInterruptedException();}intindex=--count;if(index==0){//trippedbooleanranAction=false;try{finalRunnablecommand=barrierCommand;if(command!=null)command.run();ranAction=true;nextGeneration();return0;}finally{if(!ranAction)breakBarrier();}}for(;;){try{if(!timed)trip.await();elseif(nanos>0L)nanos=trip。awaitNanos(nanos);}catch(InterruptedExceptionie){if(g==generation&&!g.broken){breakBarrier();throwie;}else{Thread.currentThread().interrupt();}}if(g.broken)thrownewBrokenBarrierException();if(g!=generation)returnindex;if(timed&&nanos<=0L){breakBarrier();thrownewTimeoutException();}}}finally{lock.unlock();}}上面的代码有点长,但它也是CyclicBarrier的核心,这里先说说上面代码的主要作用:(1)通过ReentrantLock获取独占锁(2)通过try中的Generation判断当前代是否损坏,通过Thread的interrupted方法判断线程是否中断,如果中断通过breakBarrier方法告诉其他线程。(3)if(index==0)判断当前线程是否调用了await方法,如果调用则唤醒所有之前等待的线程。这就像作为最后一个到达终点的运动员,告诉其他人比赛已经结束。(4)for(;;)循环等待。如果没有超时,它会等到它被唤醒。如果有超时,会在超时后自动唤醒。就像在马路上奔跑的运动员。没有时间限制时,他一直跑到终点。如果有时间限制,不管你有没有跑到终点,游戏就结束了。(5)通过ReentrantLock释放排他锁。这段代码阅读起来比较麻烦,因为涉及到另外两个类ReentrantLock和Generation,我们只需要知道它的作用即可。还有一点,就是线程中断了,怎么告诉其他线程的breakBarrier方法。/***Setscurrentbarriergenerationasbrokenandwakesupeveryone.*Calledonlywhileholdinglock.*/privatevoidbreakBarrier(){generation.broken=true;count=parties;trip.signalAll();}我们可以看到breakBarrier()中除了设置broken为true外,还有SignalAll将被调用以唤醒所有在CyclicBarrier中等待的线程。好了,今天的文章就先到这里。我们已经解释了CyclicBarrier的原理,其使用可以根据自己的业务场景来确定。本文转载自微信公众号“愚公要移山”,可关注下方二维码。转载本文请联系愚公移山公众号。