2010-11-12 30 views
126

我是通過java.util.concurrent API讀取,並且發現Java併發:倒計時鎖存VS循環屏障

  • CountDownLatch:一個同步輔助,允許一個或多個線程等待,直到在其他線程正在執行的一組操作完成。
  • CyclicBarrier:一種同步協助,它允許一組線程互相等待以達到共同障礙點。

對我來說,兩者似乎是平等的,但我相信還有更多。例如,CoundownLatch, the countdown value could not be reset, that can happen in the case of CyclicBarrier

兩者之間還有其他區別嗎?
什麼是use cases哪裏有人想要重置倒數的值?

+4

鎖存器等待事件;障礙是等待其他線程。 - Java Concurrency in Practice,B.Goetz et al。 – user2418306 2016-04-05 10:41:48

回答

103

一個主要區別是CyclicBarrier需要一個(可選的)Runnable任務,該任務在滿足公共障礙條件後運行。

它還可以讓您獲得在障礙處等待的客戶數量和觸發障礙所需的數量。一旦觸發屏障重置,可以再次使用。

對於簡單的用例 - 服務啓動等...一個CountdownLatch是好的。 CyclicBarrier對於更復雜的協調任務很有用。這種事情的一個例子是並行計算 - 在計算中涉及多個子任務 - 類似MapReduce

+3

「它還可以讓你獲得在屏障上等待的客戶數量和觸發屏障所需的數量,一旦觸發屏障重置並可以再次使用。」 我非常喜歡這一點。我讀過的一些文章表明,CyclicBarrier是循環的,因爲你調用了reset()方法。確實如此,但他們不經常提及的是,一旦觸發了屏障,屏障就會自動重置。我會發布一些示例代碼來說明這一點。 – 2014-04-12 06:18:13

+0

@Kevin Lee感謝「屏障被觸發後自動重置。」所以不需要在代碼中調用reset()。 – supernova 2018-01-01 00:59:31

11

主要的區別是記錄在Javadocs CountdownLatch。即:

CountDownLatch初始化爲 給定的計數。在AWAIT方法方框 直到當前計數達到零 由於調用countDown() 方法,之後所有等待 線程被釋放和立即等待返回 任何 後續調用。這是一次性的 現象 - 重置計數不能爲 。如果您需要 重置計數的版本,請考慮使用一個 CyclicBarrier。

1.6 Javadoc

+4

如果它們的區別只是可以重置或不重置,CyclicBarrier可能會更好地命名爲ResetableCountDownLatch,由於差異更有意義。 – 2011-11-04 07:31:40

99

還有另一個區別。

使用CyclicBarrier時,假設您指定觸發屏障的等待線程的數量。如果指定5,則必須至少有5個線程才能呼叫await()

當使用CountDownLatch時,您指定調用countDown()的次數,這將導致所有等待線程被釋放。這意味着您只能使用一個線程CountDownLatch

「你爲什麼要這麼做?」,你可能會說。想象一下,您正在使用由其他人執行回調編碼的神祕API。您希望您的某個線程等待某個回調被多次調用。你不知道哪個線程將被調用。在這種情況下,CountDownLatch是完美的,但我想不出用CyclicBarrier(實際上,我可以,但它涉及超時......惡作劇!)來實現這一點。

我只希望CountDownLatch可以重置!

+9

我認爲這是更好地顯示理論差異的答案。事實上鎖只能通過多次調用一個方法來解決,而障礙需要精確的線程等待()。 – flagg19 2013-08-12 13:54:19

+21

對 - 這是主要區別:CountDownLatch - > NumberOfCalls,CyclicBarrier - > NumberOfThreads – 2014-01-29 13:22:12

+0

完美的解釋! – 2014-05-23 12:10:07

3

對於CyclicBarrier,只要所有子線程開始調用barrier.await(),就會在Barrier中執行Runnable。等待每個子線程的障礙將需要不同的時間完成,並且它們都在同一時間完成。

8

CountDownLatch用於一次性同步。在使用CountDownLatch時,允許任何線程多次調用countDown()。調用await()的線程將被阻塞,直到count由於其他未阻塞的線程對countDown()的調用而達到零。的javadoc for CountDownLatch狀態:

在AWAIT方法阻塞,直到當前計數達到倒數()方法,在這之後所有等待的線程 被釋放的,由於零到 調用和立即等待返回 任何後續調用。 ...

另一種典型的用法是劃分一個問題分解成N個部分, 描述了具有一個可運行執行該部分和 遞減計數上的閂鎖每個部分,並且隊列中的所有的Runnable到執行器。 當所有子部分完成後,協調線程將能夠通過 等待。 (當線程必須這樣反覆倒計時在 ,可改爲使用CyclicBarrier。)

相反,環狀阻擋用於多個sychronization點,例如如果一組線程正在運行循環/分階段計算並需要在開始下一個迭代/階段之前進行同步。由於每javadoc for CyclicBarrier

,因爲它可以被重新使用的 等待的線程被釋放之後的阻隔被稱爲循環。

與CountDownLatch不同的是,對await()的每次調用都屬於某個階段,並可能導致線程阻塞,直到屬於該階段的所有參與方都調用await()。沒有CyclicBarrier支持的顯式countDown()操作。

30

沒有人提到過的一點是,在CyclicBarrier中,如果一個線程有問題(超時,中斷...),所有其他達到await()的人都會得到一個異常。見的Javadoc:

的的CyclicBarrier使用了失敗的同步嘗試全或無破損模式:如果一個線程離開屏障點過早,因爲中斷,失敗或超時,其他所有線程在該屏障點等待也會通過BrokenBarrierException異常離開(或者InterruptException,如果它們也幾乎在同一時間中斷)。

9

這個問題已經得到了充分的回答,但我認爲我可以通過發佈一些代碼來增加一點價值。

爲了說明循環障礙的行爲,我做了一些示例代碼。一旦關閉了障礙物,自動重置爲,以便它可以再次使用(因此它是「循環」的)。當您運行程序時,請注意打印輸出「Let's play」只有在防護欄傾斜後纔會觸發。

import java.util.concurrent.BrokenBarrierException; 
import java.util.concurrent.CyclicBarrier; 

public class CyclicBarrierCycles { 

    static CyclicBarrier barrier; 

    public static void main(String[] args) throws InterruptedException { 
     barrier = new CyclicBarrier(3); 

     new Worker().start(); 
     Thread.sleep(1000); 
     new Worker().start(); 
     Thread.sleep(1000); 
     new Worker().start(); 
     Thread.sleep(1000); 

     System.out.println("Barrier automatically resets."); 

     new Worker().start(); 
     Thread.sleep(1000); 
     new Worker().start(); 
     Thread.sleep(1000); 
     new Worker().start(); 
    } 

} 


class Worker extends Thread { 
    @Override 
    public void run() { 
     try { 
      CyclicBarrierCycles.barrier.await(); 
      System.out.println("Let's play."); 
     } catch (InterruptedException e) { 
      e.printStackTrace(); 
     } catch (BrokenBarrierException e) { 
      e.printStackTrace(); 
     } 
    } 
} 
17

我認爲JavaDoc已經明確地解釋了這些差異。 大多數人都知道CountDownLatch無法重置,但是,CyclicBarrier可以。但這不是唯一的區別,或者CyclicBarrier可以重命名爲ResetbleCountDownLatch。 我們應該告訴從自己的目標,這在JavaDoc中所描述的角度不同

CountDownLatch:一個同步輔助類,它允許一個或多個線程等待,直到其他線程中執行的一組操作完成。

CyclicBarrier:一種同步協助,它允許一組線程互相等待以達到公共障礙點。

在countDownLatch中,有一個或多個線程正在等待一組其他線程來完成。在這種情況下,有兩種類型的線程,一種類型在等待,另一種類型在做任務,在完成任務之後,他們可能正在等待或者只是終止。

在CyclicBarrier中,只有一種類型的線程,它們正在等待對方,它們是相等的。

+1

「在CyclicBarrier中,只有一種類型的線程」 ......它們在「等待的角色」上是平等的,直到其他線程調用.await(),但它們可能「在他們所做的事情上不相等」。而且它們都必須是完全不同的同一類型或不同類型的線程實例(!),而在CountDownLatch中,同一線程可能會調用countDown()並影響結果。 – 2015-05-23 14:29:45

3

一個明顯的區別是,只有N個線程可以等待N個CyclicBarrier在一個週期內釋放。但是,可以等待N個CountDownLatch的線程數量不受限制。遞減遞減可以由一個線程N次或N個線程每個或每個組合執行一次。

0

CountDownLatch,主線程等待其他線程完成其執行。在CyclicBarrier,工作者線程等待對方完成其執行。

您不能重複使用相同的CountDownLatch例如一旦計數達到零和鎖存器是開放的,在另一方面的CyclicBarrier可以通過重新設置屏障,一旦屏障被打破被重用。

3

簡而言之,只是爲了瞭解兩者鍵官能差異:

public class CountDownLatch { 
    private Object mutex = new Object(); 
    private int count; 

    public CountDownLatch(int count) { 
     this.count = count; 
    } 

    public void await() throws InterruptedException { 
     synchronized (mutex) { 
      while (count > 0) { 
       mutex.wait(); 
      } 
     } 
    } 

    public void countDown() { 
     synchronized (mutex) { 
      if (--count == 0) 
       mutex.notifyAll(); 
     } 

    } 
} 

public class CyclicBarrier { 
    private Object mutex = new Object(); 
    private int count; 

    public CyclicBarrier(int count) { 
     this.count = count; 
    } 

    public void await() throws InterruptedException { 
     synchronized (mutex) { 
      count--; 
      while(count > 0) 
       mutex.wait(); 
      mutex.notifyAll(); 
     } 
    } 
} 

當然除,設有像無阻塞,定時等待,診斷以及上述答案中詳細解釋的所有內容。

但是,上述類在其提供的功能中具有完整的功能,並且與其通信對象同名。

在不同的音符,CountDownLatch的內部類的子類AQS,而CyclicBarrier使用ReentrantLock(我懷疑它可能是周圍的其他方式或兩者可以使用AQS或兩者使用鎖 - 無的性能效率的損失)

3

當我正在研究閂鎖和循環邊欄時,我想出了這個隱喻。 cyclicbarriers:想象一個公司有一個會議室。爲了開始會議,有一定數量的與會者必須前來參加會議(以使其正式開會)。下面是一個正常的會議參加者的代碼(僱員)

class MeetingAtendee implements Runnable { 

CyclicBarrier myMeetingQuorumBarrier; 

public MeetingAtendee(CyclicBarrier myMileStoneBarrier) { 
    this.myMeetingQuorumBarrier = myMileStoneBarrier; 
} 

@Override 
public void run() { 
    try { 
     System.out.println(Thread.currentThread().getName() + " i joined the meeting ..."); 
     myMeetingQuorumBarrier.await(); 
     System.out.println(Thread.currentThread().getName()+" finally meeting stared ..."); 
    } catch (InterruptedException e) { 
     e.printStackTrace(); 
    } catch (BrokenBarrierException e) { 
     System.out.println("Meeting canceled! every body dance <by chic band!>"); 
    } 
} 
} 

員工加入會議,等待別人來啓動會議。如果會議取消,他也會退出)然後我們有THE BOSS如何不喜歡等待其他人出現,如果他放鬆了他的病人,他取消了會議。

class MeetingAtendeeTheBoss implements Runnable { 

CyclicBarrier myMeetingQuorumBarrier; 

public MeetingAtendeeTheBoss(CyclicBarrier myMileStoneBarrier) { 
    this.myMeetingQuorumBarrier = myMileStoneBarrier; 
} 

@Override 
public void run() { 
    try { 
     System.out.println(Thread.currentThread().getName() + "I am THE BOSS - i joined the meeting ..."); 
     //boss dose not like to wait too much!! he/she waits for 2 seconds and we END the meeting 
     myMeetingQuorumBarrier.await(1,TimeUnit.SECONDS); 
     System.out.println(Thread.currentThread().getName()+" finally meeting stared ..."); 
    } catch (InterruptedException e) { 
     e.printStackTrace(); 
    } catch (BrokenBarrierException e) { 
     System.out.println("what WHO canceled The meeting"); 
    } catch (TimeoutException e) { 
     System.out.println("These employees waste my time!!"); 
    } 
} 
} 

在正常的日子裏,員工來開會等待其他人出現,如果有些與會者不來,他們必須等待無限期!在一些特殊的會議上,老闆來了,他不喜歡等待(5人需要啓動會議,但只有老闆來了,也是一位熱心的員工),因此他取消會議(氣憤地)

CyclicBarrier meetingAtendeeQuorum = new CyclicBarrier(5); 
Thread atendeeThread = new Thread(new MeetingAtendee(meetingAtendeeQuorum)); 
Thread atendeeThreadBoss = new Thread(new MeetingAtendeeTheBoss(meetingAtendeeQuorum)); 
    atendeeThread.start(); 
    atendeeThreadBoss.start(); 

輸出:

//Thread-1I am THE BOSS - i joined the meeting ... 
// Thread-0 i joined the meeting ... 
// These employees waste my time!! 
// Meeting canceled! every body dance <by chic band!> 

還有另一種場外線程(地震)取消會議(呼叫重置方法)的情況。在這種情況下,所有等待的線程都會被異常喚醒。

class NaturalDisasters implements Runnable { 

CyclicBarrier someStupidMeetingAtendeeQuorum; 

public NaturalDisasters(CyclicBarrier someStupidMeetingAtendeeQuorum) { 
    this.someStupidMeetingAtendeeQuorum = someStupidMeetingAtendeeQuorum; 
} 

void earthQuakeHappening(){ 
    System.out.println("earth quaking....."); 
    someStupidMeetingAtendeeQuorum.reset(); 
} 

@Override 
public void run() { 
    earthQuakeHappening(); 
} 
} 

運行的代碼將導致搞笑輸出:

// Thread-1I am THE BOSS - i joined the meeting ... 
// Thread-0 i joined the meeting ... 
// earth quaking..... 
// what WHO canceled The meeting 
// Meeting canceled! every body dance <by chic band!> 

您還可以添加一個祕書的會議室,如果會議舉行,她將記錄每一次的事情,但她不是會議的一部分:

class MeetingSecretary implements Runnable { 

@Override 
public void run() { 
     System.out.println("preparing meeting documents"); 
     System.out.println("taking notes ..."); 
} 
} 

閉鎖:如果憤怒的老闆要舉辦個展爲公司客戶,每一件事情需要準備好(資源)。我們提供了一份待辦事項清單,每個工作人員(主題)的工作量和我們檢查待辦事項清單(一些工作人員做繪畫,其他人準備音響系統...)。當待辦事項列表中的所有項目都已完成(提供資源)時,我們可以爲客戶打開大門。

public class Visitor implements Runnable{ 

CountDownLatch exhibitonDoorlatch = null; 

public Visitor (CountDownLatch latch) { 
    exhibitonDoorlatch = latch; 
} 

public void run() { 
    try { 
     exhibitonDoorlatch .await(); 
    } catch (InterruptedException e) { 
     e.printStackTrace(); 
    } 

    System.out.println("customer visiting exebition"); 
} 
} 

,工人如何準備展覽:

class Worker implements Runnable { 

CountDownLatch myTodoItem = null; 

public Worker(CountDownLatch latch) { 
    this.myTodoItem = latch; 
} 

public void run() { 
     System.out.println("doing my part of job ..."); 
     System.out.println("My work is done! remove it from todo list"); 
     myTodoItem.countDown(); 
} 
} 

    CountDownLatch preperationTodoList = new CountDownLatch(3); 

    // exhibition preparation workers 
    Worker  electricalWorker  = new Worker(preperationTodoList); 
    Worker  paintingWorker  = new Worker(preperationTodoList); 

    // Exhibition Visitors 
    ExhibitionVisitor exhibitionVisitorA = new ExhibitionVisitor(preperationTodoList); 
    ExhibitionVisitor exhibitionVisitorB = new ExhibitionVisitor(preperationTodoList); 
    ExhibitionVisitor exhibitionVisitorC = new ExhibitionVisitor(preperationTodoList); 

    new Thread(electricalWorker).start(); 
    new Thread(paintingWorker).start(); 

    new Thread(exhibitionVisitorA).start(); 
    new Thread(exhibitionVisitorB).start(); 
    new Thread(exhibitionVisitorC).start();