Đồng thời Java: Chốt đếm ngược so với hàng rào Cyclic


160

Tôi đã đọc qua java.util.conc hiện API và thấy rằng

  • CountDownLatch: Một trợ giúp đồng bộ hóa cho phép một hoặc nhiều luồng chờ cho đến khi một tập hợp các hoạt động được thực hiện trong các luồng khác hoàn thành.
  • CyclicBarrier: Một trợ giúp đồng bộ hóa cho phép tất cả các chuỗi xử lý chờ nhau để đạt đến một điểm rào cản chung.

Đối với tôi cả hai dường như bằng nhau, nhưng tôi chắc chắn có nhiều hơn thế.

Ví dụ, trong CoundownLatch, the countdown value could not be reset, that can happen in the case of CyclicBarrier.

Có sự khác biệt nào khác giữa hai người không?
Đâu là use casesnơi mà ai đó muốn đặt lại giá trị của đếm ngược?


12
Chốt là để chờ đợi cho các sự kiện; rào cản là chờ đợi cho chủ đề khác. - Đồng thời Java trong thực tiễn, B.Goetz et al.
dùng2418306

Câu trả lời:


137

Một điểm khác biệt chính là CyclicBarrier nhận một nhiệm vụ có thể chạy (tùy chọn) được chạy khi điều kiện rào cản chung được đáp ứng.

Nó cũng cho phép bạn có được số lượng khách hàng đang chờ ở rào chắn và số lượng cần thiết để kích hoạt rào cản. Sau khi kích hoạt, rào cản được thiết lập lại và có thể được sử dụng lại.

Đối với các trường hợp sử dụng đơn giản - các dịch vụ bắt đầu, v.v ... CountdownLatch vẫn ổn. Một CyclicBarrier rất hữu ích cho các nhiệm vụ phối hợp phức tạp hơn. Một ví dụ về một điều như vậy sẽ là tính toán song song - trong đó nhiều nhiệm vụ được tham gia vào tính toán - giống như MapReduce .


6
"Nó cũng cho phép bạn có được số lượng khách hàng đang chờ ở rào chắn và số lượng cần thiết để kích hoạt rào cản. Sau khi kích hoạt, rào cản được đặt lại và có thể được sử dụng lại." Tôi thực sự thích điểm này. Một vài bài báo tôi đã đọc đề xuất rằng CyclicBarrier là tuần hoàn vì bạn gọi phương thức reset (). Điều đó là đúng, nhưng điều họ không thường đề cập là rào cản được thiết lập lại tự động ngay khi được kích hoạt. Tôi sẽ đăng một số mã mẫu để minh họa điều này.
Kevin Lee

@Kevin Lee Cảm ơn vì "rào cản được thiết lập lại tự động ngay khi được kích hoạt." vì vậy không cần phải gọi reset () trong mã.
siêu tân tinh

134

Có một sự khác biệt khác.

Khi sử dụng a CyclicBarrier, giả định là bạn chỉ định số lượng luồng chờ kích hoạt rào cản. Nếu bạn chỉ định 5, bạn phải có ít nhất 5 luồng để gọi await().

Khi sử dụng a CountDownLatch, bạn chỉ định số lượng cuộc gọi đến countDown()đó sẽ dẫn đến tất cả các chuỗi chờ được phát hành. Điều này có nghĩa là bạn có thể sử dụng một CountDownLatchchỉ với một chủ đề duy nhất.

"Tại sao bạn sẽ làm điều đó?", Bạn có thể nói. Hãy tưởng tượng rằng bạn đang sử dụng một API bí ẩn được mã hóa bởi người khác thực hiện các cuộc gọi lại. Bạn muốn một trong các chủ đề của bạn chờ cho đến khi một cuộc gọi lại nhất định đã được gọi một số lần. Bạn không biết chủ đề nào mà cuộc gọi lại sẽ được gọi. Trong trường hợp này, a CountDownLatchlà hoàn hảo, trong khi tôi không thể nghĩ ra bất kỳ cách nào để thực hiện điều này bằng cách sử dụng CyclicBarrier(thực ra, tôi có thể, nhưng nó liên quan đến thời gian chờ ... yuck!).

Tôi chỉ muốn CountDownLatchcó thể được thiết lập lại!


10
Tôi nghĩ rằng đây là câu trả lời cho thấy tốt hơn sự khác biệt về lý thuyết. Thực tế là các chốt có thể bị phá vỡ bằng cách chỉ gọi nhiều lần một phương thức trong khi các rào cản cần một loạt các luồng chính xác để chờ ().
cờg19

43
Phải - đó là sự khác biệt chính: CountDownLatch -> NumberOfCalls, CyclicBarrier -> NumberOfThreads
Ivan Voroshilin

1
Tôi đồng ý rằng sẽ rất tuyệt CountDownLatchkhi có thể đặt lại - một cách giải quyết mà tôi sử dụng để thực hiện thông báo chờ thô chỉ là mới CountDownLatchngay lập tức khi khối mã được bảo vệ được nhập (khi chốt đạt đến 0). Điều này không áp dụng trong tất cả các trường hợp / phạm vi tất nhiên, nhưng tôi nghĩ rằng đáng chú ý rằng đó là một lựa chọn trong các tình huống goldilocks.
Ephemera

2
Một trong những câu trả lời tốt nhất về chủ đề này. Java Concurrency in Practice- nói điều tương tự : Latches are for waiting for events; barriers are for waiting for other threads.. Một điểm chính và thiết yếu để hiểu sự khác biệt giữa hai.
Rahul Dev Mishra

Tài liệu Java 8 cho biết "Một CountDownLatch được khởi tạo thành N có thể được sử dụng để làm cho một luồng chờ cho đến khi N luồng hoàn thành một số hành động hoặc một số hành động đã được hoàn thành N lần." dường như đối với tôi: CountDownLatch -> NumberOfCalls Hoặc CountDownLatch -> NumberOfThreads
nir

41

Một điểm mà chưa ai đề cập đến là, trong một CyclicBarrier, nếu một luồng có vấn đề (hết thời gian, bị gián đoạn ...), tất cả những thứ khác đã đạt await()được đều có ngoại lệ. Xem Javadoc:

CyclicBarrier sử dụng mô hình phá vỡ tất cả hoặc không cho các lần thử đồng bộ hóa không thành công: Nếu một luồng rời khỏi điểm rào cản sớm do bị gián đoạn, thất bại hoặc hết thời gian, tất cả các luồng khác đang chờ tại điểm rào cản đó cũng sẽ rời đi một cách bất thường thông qua BrokenBarrierException (hoặc bị gián đoạn nếu chúng cũng bị gián đoạn cùng một lúc).


22

Tôi nghĩ rằng JavaDoc đã giải thích rõ ràng về sự khác biệt. Hầu hết mọi người biết rằng CountDownLatch không thể được thiết lập lại, tuy nhiên, CyclicBarrier có thể. Nhưng đây không phải là sự khác biệt duy nhất hoặc CyclicBarrier có thể được đổi tên thành ResetbleCountDownLatch. Chúng ta nên nói sự khác biệt từ quan điểm của các mục tiêu của chúng, được mô tả trong JavaDoc

CountDownLatch: Một trợ giúp đồng bộ hóa cho phép một hoặc nhiều luồng chờ cho đến khi một tập hợp các hoạt động được thực hiện trong các luồng khác hoàn thành.

CyclicBarrier: Một hỗ trợ đồng bộ hóa cho phép tất cả các chuỗi xử lý chờ nhau để đạt đến một điểm rào cản chung.

Trong CountDownLatch, có một hoặc nhiều luồng, đang chờ một tập hợp các luồng khác hoàn thành. Trong tình huống này, có hai loại luồng, một loại đang chờ, một loại khác đang làm gì đó, sau khi hoàn thành nhiệm vụ, chúng có thể chờ hoặc chỉ chấm dứt.

Trong CyclicBarrier, chỉ có một loại luồng, chúng đang chờ nhau, chúng bằng nhau.


1
"Trong CyclicBarrier, chỉ có một loại chuỗi" ... Chúng bằng nhau trong "vai trò chờ đợi" cho đến khi các luồng khác gọi .await (), nhưng chúng có thể "không bằng những gì chúng làm". Ngoài ra, tất cả chúng phải là các thể hiện luồng hoàn toàn khác nhau (!) Cùng loại hoặc cùng loại khác nhau, trong khi trong CountDownLatch, cùng một luồng có thể gọi CountDown () và ảnh hưởng đến kết quả.
Vladimir Nabokov

Tôi đồng ý rằng CountDownLatch vốn đã yêu cầu hai vai trò: một khách hàng cho CountDown và một khách hàng để chờ đợi. Mặt khác, các máy khách CyclicBarrier có thể hoạt động tốt với phương thức chờ đợi.
isaolmez

14

Sự khác biệt chính được ghi lại ngay trong Javadocs cho CountdownLatch. Cụ thể là:

Một CountDownLatch được khởi tạo với một số lượng nhất định. Các phương thức chờ đợi chặn cho đến khi tổng số hiện tại bằng không do các yêu cầu của phương thức CountDown (), sau đó tất cả các chuỗi chờ được phát hành và bất kỳ yêu cầu tiếp theo nào đang chờ trả về ngay lập tức. Đây là hiện tượng một lần - không thể thiết lập lại số đếm. Nếu bạn cần một phiên bản đặt lại số đếm, hãy xem xét sử dụng CyclicBarrier.

nguồn 1.6 Javadoc


4
Nếu sự khác biệt của chúng chỉ là có thể được thiết lập lại hoặc không, CyclicBarrier có thể được đặt tên tốt hơn là ResetableCountDownLatch, điều này có ý nghĩa hơn do sự khác biệt.
James.Xu

12

CountDownLatch được sử dụng để đồng bộ hóa một lần. Trong khi sử dụng CountDownLatch, bất kỳ luồng nào cũng được phép gọi CountDown () bao nhiêu lần tùy thích. Các chủ đề được gọi là await () bị chặn cho đến khi tổng số bằng 0 do các lệnh gọi tới CountDown () bởi các chủ đề được bỏ chặn khác. Các javadoc cho trạng thái CountDownLatch :

Các phương thức chờ đợi chặn cho đến khi tổng số hiện tại bằng không do các yêu cầu của phương thức CountDown (), sau đó tất cả các chuỗi chờ được phát hành và bất kỳ yêu cầu tiếp theo nào đang chờ trả về ngay lập tức. ...

Một cách sử dụng thông thường khác là chia một vấn đề thành N phần, mô tả từng phần bằng Runnable thực thi phần đó và đếm ngược trên chốt và xếp hàng tất cả các Runnables cho Executor. Khi tất cả các phần phụ hoàn thành, luồng phối hợp sẽ có thể đi qua chờ đợi. (Khi các luồng phải liên tục đếm ngược theo cách này, thay vào đó hãy sử dụng CyclicBarrier.)

Ngược lại, hàng rào tuần hoàn được sử dụng cho nhiều điểm đồng bộ hóa, ví dụ: nếu một tập hợp các luồng đang chạy tính toán vòng / pha và cần phải đồng bộ hóa trước khi bắt đầu giai đoạn lặp / pha tiếp theo. Theo javadoc cho CyclicBarrier :

Rào chắn được gọi là tuần hoàn vì nó có thể được sử dụng lại sau khi các luồng chờ được giải phóng.

Không giống như CountDownLatch, mỗi lệnh gọi await () thuộc về một số pha và có thể khiến luồng bị chặn cho đến khi tất cả các bên thuộc pha đó đã gọi await (). Không có hoạt động CountDown () rõ ràng được hỗ trợ bởi CyclicBarrier.


12

Câu hỏi này đã được trả lời thỏa đáng rồi, nhưng tôi nghĩ rằng tôi có thể thêm giá trị bằng cách đăng một số mã.

Để minh họa hành vi của hàng rào tuần hoàn, tôi đã tạo một số mã mẫu. Ngay khi rào cản được nghiêng, nó sẽ tự động được đặt lại để có thể sử dụng lại (do đó nó là "chu kỳ"). Khi bạn chạy chương trình, hãy quan sát rằng bản in "Hãy chơi" chỉ được kích hoạt sau khi vượt qua rào cản.

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();
        }
    }
}

8

Khi tôi đang nghiên cứu về Latches và cyclicbarrier, tôi đã nghĩ ra những ẩn dụ này. cyclicbarrier : Hãy tưởng tượng một công ty có một phòng họp. Để bắt đầu cuộc họp, một số lượng người tham dự cuộc họp nhất định phải đến cuộc họp (để chính thức). Sau đây là mã của một người tham dự cuộc họp bình thường (một nhân viên)

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!>");
    }
 }
}

nhân viên tham gia cuộc họp, chờ người khác đến để bắt đầu cuộc họp. Ngoài ra, anh ta sẽ thoát nếu cuộc họp bị hủy bỏ :) sau đó chúng tôi có BOSS làm thế nào các liều không muốn chờ đợi người khác xuất hiện và nếu anh ta mất bệnh nhân của mình, anh ta hủy bỏ cuộc họp.

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!!");
    }
 }
}

Vào một ngày bình thường, nhân viên đến họp chờ người khác xuất hiện và nếu một số người tham dự không đến thì họ phải chờ vô thời hạn! trong một số cuộc họp đặc biệt, ông chủ đến và anh ta không muốn chờ đợi (5 người cần bắt đầu cuộc họp nhưng chỉ có sếp đến và cũng là một nhân viên nhiệt tình) nên anh ta hủy cuộc họp (giận dữ)

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

Đầu ra:

//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!>

Có một kịch bản khác trong đó một luồng ngoài khác (một trận động đất) hủy cuộc họp (phương thức đặt lại cuộc gọi). trong trường hợp này, tất cả các chuỗi chờ được đánh thức bởi một ngoại lệ.

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();
 }
}

mã chạy sẽ dẫn đến đầu ra buồn cười:

// 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!>

Bạn cũng có thể thêm một thư ký vào phòng họp, nếu một cuộc họp được tổ chức, cô ấy sẽ ghi chép lại mọi thứ nhưng cô ấy không phải là một phần của cuộc họp:

class MeetingSecretary implements Runnable {

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

Chốt : nếu ông chủ tức giận muốn tổ chức triển lãm cho khách hàng của công ty, mọi thứ cần phải sẵn sàng (tài nguyên). chúng tôi cung cấp một danh sách việc cần làm mỗi công nhân (Chủ đề) làm công việc của mình và chúng tôi kiểm tra danh sách việc cần làm (một số công nhân vẽ tranh, những người khác chuẩn bị hệ thống âm thanh ...). khi tất cả các mục trong danh sách việc cần làm đã hoàn thành (tài nguyên được cung cấp), chúng tôi có thể mở cửa cho khách hàng.

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");
 }
}

Và các công nhân đang chuẩn bị triển lãm như thế nào:

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();

6

Tóm lại , chỉ cần hiểu sự khác biệt về chức năng chính giữa hai:

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();
        }
    }
}

tất nhiên, ngoại trừ các tính năng như không chặn, chờ đợi theo thời gian, chẩn đoán và mọi thứ đã được giải thích chi tiết trong các câu trả lời trên.

Tuy nhiên, các lớp trên là đầy đủ chức năng và tương đương, trong chức năng được cung cấp, với tên tương ứng của chúng.

Ở một lưu ý khác, CountDownLatchcác lớp con bên trong của lớp AQS, trong khi CyclicBarriersử dụng ReentrantLock(sự nghi ngờ của tôi là nó có thể khác hoặc cả hai có thể sử dụng AQS hoặc cả hai đều sử dụng Khóa - mà không làm giảm hiệu quả hiệu suất)


5

Một sự khác biệt rõ ràng là, chỉ có các luồng N có thể chờ trên CyclicBarrier của N được phát hành trong một chu kỳ. Nhưng số lượng luồng không giới hạn có thể chờ trên CountDownLatch của N. Việc giảm xuống có thể được thực hiện bởi một luồng N lần hoặc N luồng một lần mỗi lần hoặc kết hợp.


4

Trong trường hợp của CyclicBarrier, ngay khi TẤT CẢ các luồng con bắt đầu gọi rào cản.await (), Runnable được thực thi trong Rào chắn. Rào chắn.await trong mỗi luồng con sẽ mất nhiều thời gian khác nhau để hoàn thành, và tất cả chúng đều kết thúc cùng một lúc.


4

Trong CountDownLatch , các luồng chính chờ các luồng khác hoàn thành việc thực hiện. Trong CyclicBarrier , các luồng công nhân chờ nhau hoàn thành việc thực hiện.

Bạn không thể sử dụng lại cùng một ví dụ CountDownLatch khi số đếm đạt đến 0 và chốt được mở, mặt khác CyclicBarrier có thể được sử dụng lại bằng cách đặt lại Barrier, Một khi rào cản bị phá vỡ.


Nó không cần phải là chủ đề chính. Nó có thể là bất kỳ luồng nào tạo ra CountDownLatch và chia sẻ nó với các luồng không chính khác.
Aniket Thakur

1

CountDownLatch là một đếm ngược của bất cứ điều gì; CyclicBarrier là một đếm ngược chỉ cho chủ đề

giả sử có 5 luồng công nhân và một luồng giao hàng, và khi công nhân sản xuất 100 mặt hàng, người giao hàng sẽ chuyển chúng ra.

Đối với CountDownLatch, quầy có thể là công nhân hoặc vật phẩm

Đối với CyclicBarrier, bộ đếm chỉ có thể trên công nhân

Nếu một công nhân rơi vào giấc ngủ vô hạn, với CountDownLatch trên các vật phẩm, Shipper có thể giao hàng; Tuy nhiên, với CyclicBarrier, Shipper không bao giờ có thể được gọi


0

@Kevin Lee và @Jon Tôi đã thử CyclicBarrier với tùy chọn Runnable. Có vẻ như nó chạy vào đầu và sau khi CyclicBarrier được lật. Đây là mã và đầu ra

hàng rào CyclicBarrier tĩnh;

    public static void main(String[] args) throws InterruptedException {
        barrier = new CyclicBarrier(3, new Runnable() {
            @Override
            public void run() {
                System.out.println("I run in the beginning and after the CyclicBarrier is tipped");
            }
        });

        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();
    }

Đầu ra

I run in the beginning and after the CyclicBarrier is tipped
Let's play.
Let's play.
Let's play.
Barrier automatically resets.
I run in the beginning and after the CyclicBarrier is tipped
Let's play.
Let's play.
Let's play.
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.