現(xiàn)實(shí)生活中我們經(jīng)常會遇到這樣的情景,在進(jìn)行某個(gè)活動前需要等待人全部都齊了才開始。例如吃飯時(shí)要等全家人都上座了才動筷子,旅游時(shí)要等全部人都到齊了才出發(fā),比賽時(shí)要等運(yùn)動員都上場后才開始。在JUC包中為我們提供了一個(gè)同步工具類能夠很好的模擬這類場景,它就是CyclicBarrier類。利用CyclicBarrier類可以實(shí)現(xiàn)一組線程相互等待,當(dāng)所有線程都到達(dá)某個(gè)屏障點(diǎn)后再進(jìn)行后續(xù)的操作。下圖演示了這一過程。
在網(wǎng)站建設(shè)、網(wǎng)站制作過程中,需要針對客戶的行業(yè)特點(diǎn)、產(chǎn)品特性、目標(biāo)受眾和市場情況進(jìn)行定位分析,以確定網(wǎng)站的風(fēng)格、色彩、版式、交互等方面的設(shè)計(jì)方向。創(chuàng)新互聯(lián)還需要根據(jù)客戶的需求進(jìn)行功能模塊的開發(fā)和設(shè)計(jì),包括內(nèi)容管理、前臺展示、用戶權(quán)限管理、數(shù)據(jù)統(tǒng)計(jì)和安全保護(hù)等功能。
在CyclicBarrier類的內(nèi)部有一個(gè)計(jì)數(shù)器,每個(gè)線程在到達(dá)屏障點(diǎn)的時(shí)候都會調(diào)用await方法將自己阻塞,此時(shí)計(jì)數(shù)器會減1,當(dāng)計(jì)數(shù)器減為0的時(shí)候所有因調(diào)用await方法而被阻塞的線程將被喚醒。這就是實(shí)現(xiàn)一組線程相互等待的原理,下面我們先看看CyclicBarrier有哪些成員變量。
//同步操作鎖 private final ReentrantLock lock = new ReentrantLock(); //線程攔截器 private final Condition trip = lock.newCondition(); //每次攔截的線程數(shù) private final int parties; //換代前執(zhí)行的任務(wù) private final Runnable barrierCommand; //表示柵欄的當(dāng)前代 private Generation generation = new Generation(); //計(jì)數(shù)器 private int count; //靜態(tài)內(nèi)部類Generation private static class Generation { boolean broken = false; }
上面貼出了CyclicBarrier所有的成員變量,可以看到CyclicBarrier內(nèi)部是通過條件隊(duì)列trip來對線程進(jìn)行阻塞的,并且其內(nèi)部維護(hù)了兩個(gè)int型的變量parties和count,parties表示每次攔截的線程數(shù),該值在構(gòu)造時(shí)進(jìn)行賦值。count是內(nèi)部計(jì)數(shù)器,它的初始值和parties相同,以后隨著每次await方法的調(diào)用而減1,直到減為0就將所有線程喚醒。CyclicBarrier有一個(gè)靜態(tài)內(nèi)部類Generation,該類的對象代表柵欄的當(dāng)前代,就像玩游戲時(shí)代表的本局游戲,利用它可以實(shí)現(xiàn)循環(huán)等待。barrierCommand表示換代前執(zhí)行的任務(wù),當(dāng)count減為0時(shí)表示本局游戲結(jié)束,需要轉(zhuǎn)到下一局。在轉(zhuǎn)到下一局游戲之前會將所有阻塞的線程喚醒,在喚醒所有線程之前你可以通過指定barrierCommand來執(zhí)行自己的任務(wù)。接下來我們看看它的構(gòu)造器。
//構(gòu)造器1 public CyclicBarrier(int parties, Runnable barrierAction) { if (parties <= 0) throw new IllegalArgumentException(); this.parties = parties; this.count = parties; this.barrierCommand = barrierAction; } //構(gòu)造器2 public CyclicBarrier(int parties) { this(parties, null); }
CyclicBarrier有兩個(gè)構(gòu)造器,其中構(gòu)造器1是它的核心構(gòu)造器,在這里你可以指定本局游戲的參與者數(shù)量(要攔截的線程數(shù))以及本局結(jié)束時(shí)要執(zhí)行的任務(wù),還可以看到計(jì)數(shù)器count的初始值被設(shè)置為parties。CyclicBarrier類最主要的功能就是使先到達(dá)屏障點(diǎn)的線程阻塞并等待后面的線程,其中它提供了兩種等待的方法,分別是定時(shí)等待和非定時(shí)等待。
//非定時(shí)等待 public int await() throws InterruptedException, BrokenBarrierException { try { return dowait(false, 0L); } catch (TimeoutException toe) { throw new Error(toe); } } //定時(shí)等待 public int await(long timeout, TimeUnit unit) throws InterruptedException, BrokenBarrierException, TimeoutException { return dowait(true, unit.toNanos(timeout)); }
可以看到不管是定時(shí)等待還是非定時(shí)等待,它們都調(diào)用了dowait方法,只不過是傳入的參數(shù)不同而已。下面我們就來看看dowait方法都做了些什么。
//核心等待方法 private int dowait(boolean timed, long nanos) throws InterruptedException, BrokenBarrierException, TimeoutException { final ReentrantLock lock = this.lock; lock.lock(); try { final Generation g = generation; //檢查當(dāng)前柵欄是否被打翻 if (g.broken) { throw new BrokenBarrierException(); } //檢查當(dāng)前線程是否被中斷 if (Thread.interrupted()) { //如果當(dāng)前線程被中斷會做以下三件事 //1.打翻當(dāng)前柵欄 //2.喚醒攔截的所有線程 //3.拋出中斷異常 breakBarrier(); throw new InterruptedException(); } //每次都將計(jì)數(shù)器的值減1 int index = --count; //計(jì)數(shù)器的值減為0則需喚醒所有線程并轉(zhuǎn)換到下一代 if (index == 0) { boolean ranAction = false; try { //喚醒所有線程前先執(zhí)行指定的任務(wù) final Runnable command = barrierCommand; if (command != null) { command.run(); } ranAction = true; //喚醒所有線程并轉(zhuǎn)到下一代 nextGeneration(); return 0; } finally { //確保在任務(wù)未成功執(zhí)行時(shí)能將所有線程喚醒 if (!ranAction) { breakBarrier(); } } } //如果計(jì)數(shù)器不為0則執(zhí)行此循環(huán) for (;;) { try { //根據(jù)傳入的參數(shù)來決定是定時(shí)等待還是非定時(shí)等待 if (!timed) { trip.await(); }else if (nanos > 0L) { nanos = trip.awaitNanos(nanos); } } catch (InterruptedException ie) { //若當(dāng)前線程在等待期間被中斷則打翻柵欄喚醒其他線程 if (g == generation && ! g.broken) { breakBarrier(); throw ie; } else { //若在捕獲中斷異常前已經(jīng)完成在柵欄上的等待, 則直接調(diào)用中斷操作 Thread.currentThread().interrupt(); } } //如果線程因?yàn)榇蚍瓥艡诓僮鞫粏拘褎t拋出異常 if (g.broken) { throw new BrokenBarrierException(); } //如果線程因?yàn)閾Q代操作而被喚醒則返回計(jì)數(shù)器的值 if (g != generation) { return index; } //如果線程因?yàn)闀r(shí)間到了而被喚醒則打翻柵欄并拋出異常 if (timed && nanos <= 0L) { breakBarrier(); throw new TimeoutException(); } } } finally { lock.unlock(); } }
上面貼出的代碼中注釋都比較詳細(xì),我們只挑一些重要的來講??梢钥吹皆赿owait方法中每次都將count減1,減完后立馬進(jìn)行判斷看看是否等于0,如果等于0的話就會先去執(zhí)行之前指定好的任務(wù),執(zhí)行完之后再調(diào)用nextGeneration方法將柵欄轉(zhuǎn)到下一代,在該方法中會將所有線程喚醒,將計(jì)數(shù)器的值重新設(shè)為parties,最后會重新設(shè)置柵欄代次,在執(zhí)行完nextGeneration方法之后就意味著游戲進(jìn)入下一局。如果計(jì)數(shù)器此時(shí)還不等于0的話就進(jìn)入for循環(huán),根據(jù)參數(shù)來決定是調(diào)用trip.awaitNanos(nanos)還是trip.await()方法,這兩方法對應(yīng)著定時(shí)和非定時(shí)等待。如果在等待過程中當(dāng)前線程被中斷就會執(zhí)行breakBarrier方法,該方法叫做打破柵欄,意味著游戲在中途被掐斷,設(shè)置generation的broken狀態(tài)為true并喚醒所有線程。同時(shí)這也說明在等待過程中有一個(gè)線程被中斷整盤游戲就結(jié)束,所有之前被阻塞的線程都會被喚醒。線程醒來后會執(zhí)行下面三個(gè)判斷,看看是否因?yàn)檎{(diào)用breakBarrier方法而被喚醒,如果是則拋出異常;看看是否是正常的換代操作而被喚醒,如果是則返回計(jì)數(shù)器的值;看看是否因?yàn)槌瑫r(shí)而被喚醒,如果是的話就調(diào)用breakBarrier打破柵欄并拋出異常。這里還需要注意的是,如果其中有一個(gè)線程因?yàn)榈却瑫r(shí)而退出,那么整盤游戲也會結(jié)束,其他線程都會被喚醒。下面貼出nextGeneration方法和breakBarrier方法的具體代碼。
//切換柵欄到下一代 private void nextGeneration() { //喚醒條件隊(duì)列所有線程 trip.signalAll(); //設(shè)置計(jì)數(shù)器的值為需要攔截的線程數(shù) count = parties; //重新設(shè)置柵欄代次 generation = new Generation(); } //打翻當(dāng)前柵欄 private void breakBarrier() { //將當(dāng)前柵欄狀態(tài)設(shè)置為打翻 generation.broken = true; //設(shè)置計(jì)數(shù)器的值為需要攔截的線程數(shù) count = parties; //喚醒所有線程 trip.signalAll(); }
上面我們已經(jīng)通過源碼將CyclicBarrier的原理基本都講清楚了,下面我們就通過一個(gè)賽馬的例子來深入掌握它的使用。
class Horse implements Runnable { private static int counter = 0; private final int id = counter++; private int strides = 0; private static Random rand = new Random(47); private static CyclicBarrier barrier; public Horse(CyclicBarrier b) { barrier = b; } @Override public void run() { try { while(!Thread.interrupted()) { synchronized(this) { //賽馬每次隨機(jī)跑幾步 strides += rand.nextInt(3); } barrier.await(); } } catch(Exception e) { e.printStackTrace(); } } public String tracks() { StringBuilder s = new StringBuilder(); for(int i = 0; i < getStrides(); i++) { s.append("*"); } s.append(id); return s.toString(); } public synchronized int getStrides() { return strides; } public String toString() { return "Horse " + id + " "; } } public class HorseRace implements Runnable { private static final int FINISH_LINE = 75; private static List<Horse> horses = new ArrayList<Horse>(); private static ExecutorService exec = Executors.newCachedThreadPool(); @Override public void run() { StringBuilder s = new StringBuilder(); //打印賽道邊界 for(int i = 0; i < FINISH_LINE; i++) { s.append("="); } System.out.println(s); //打印賽馬軌跡 for(Horse horse : horses) { System.out.println(horse.tracks()); } //判斷是否結(jié)束 for(Horse horse : horses) { if(horse.getStrides() >= FINISH_LINE) { System.out.println(horse + "won!"); exec.shutdownNow(); return; } } //休息指定時(shí)間再到下一輪 try { TimeUnit.MILLISECONDS.sleep(200); } catch(InterruptedException e) { System.out.println("barrier-action sleep interrupted"); } } public static void main(String[] args) { CyclicBarrier barrier = new CyclicBarrier(7, new HorseRace()); for(int i = 0; i < 7; i++) { Horse horse = new Horse(barrier); horses.add(horse); exec.execute(horse); } } }
該賽馬程序主要是通過在控制臺不停的打印各賽馬的當(dāng)前軌跡,以此達(dá)到動態(tài)顯示的效果。整場比賽有多個(gè)輪次,每一輪次各個(gè)賽馬都會隨機(jī)走上幾步然后調(diào)用await方法進(jìn)行等待,當(dāng)所有賽馬走完一輪的時(shí)候?qū)?zhí)行任務(wù)將所有賽馬的當(dāng)前軌跡打印到控制臺上。這樣每一輪下來各賽馬的軌跡都在不停的增長,當(dāng)其中某個(gè)賽馬的軌跡最先增長到指定的值的時(shí)候?qū)Y(jié)束整場比賽,該賽馬成為整場比賽的勝利者!程序的運(yùn)行結(jié)果如下:
至此我們難免會將CyclicBarrier與CountDownLatch進(jìn)行一番比較。這兩個(gè)類都可以實(shí)現(xiàn)一組線程在到達(dá)某個(gè)條件之前進(jìn)行等待,它們內(nèi)部都有一個(gè)計(jì)數(shù)器,當(dāng)計(jì)數(shù)器的值不斷的減為0的時(shí)候所有阻塞的線程將會被喚醒。有區(qū)別的是CyclicBarrier的計(jì)數(shù)器由自己控制,而CountDownLatch的計(jì)數(shù)器則由使用者來控制,在CyclicBarrier中線程調(diào)用await方法不僅會將自己阻塞還會將計(jì)數(shù)器減1,而在CountDownLatch中線程調(diào)用await方法只是將自己阻塞而不會減少計(jì)數(shù)器的值。另外,CountDownLatch只能攔截一輪,而CyclicBarrier可以實(shí)現(xiàn)循環(huán)攔截。一般來說用CyclicBarrier可以實(shí)現(xiàn)CountDownLatch的功能,而反之則不能,例如上面的賽馬程序就只能使用CyclicBarrier來實(shí)現(xiàn)??傊@兩個(gè)類的異同點(diǎn)大致如此,至于何時(shí)使用CyclicBarrier,何時(shí)使用CountDownLatch,還需要讀者自己去拿捏。
以上就是本文的全部內(nèi)容,希望對大家的學(xué)習(xí)有所幫助,也希望大家多多支持創(chuàng)新互聯(lián)。
分享文章:Java并發(fā)系列之CyclicBarrier源碼分析
文章位置:http://jinyejixie.com/article40/ijjjeo.html
成都網(wǎng)站建設(shè)公司_創(chuàng)新互聯(lián),為您提供網(wǎng)站建設(shè)、定制網(wǎng)站、靜態(tài)網(wǎng)站、移動網(wǎng)站建設(shè)、App開發(fā)、響應(yīng)式網(wǎng)站
聲明:本網(wǎng)站發(fā)布的內(nèi)容(圖片、視頻和文字)以用戶投稿、用戶轉(zhuǎn)載內(nèi)容為主,如果涉及侵權(quán)請盡快告知,我們將會在第一時(shí)間刪除。文章觀點(diǎn)不代表本網(wǎng)站立場,如需處理請聯(lián)系客服。電話:028-86922220;郵箱:631063699@qq.com。內(nèi)容未經(jīng)允許不得轉(zhuǎn)載,或轉(zhuǎn)載時(shí)需注明來源: 創(chuàng)新互聯(lián)