Count Down Latches trong Java

Trong một số tình huống, chúng ta cần start ứng dụng chỉ khi một số task đã hoàn thành. Các task đó có thể chạy song song và hoàn thành cùng nhau hoặc khác thời gian. Khi đó, làm cách nào chúng ta có thể báo hiệu cho các thread khác biết các task đó đã hoàn thành hay làm sao để theo dõi xem các task chỉ định đó đã hoàn thành hay chưa? CountDownLatch là một class chỉ dành cho việc đó.
Chúng ta có thể định nghĩa CountDownLatch trong ứng dụng như một class giữ bộ đếm của chính nó. Bộ đếm sẽ bắt đầu đếm số lượng các thread cần phải đợi trước khi thông báo cho các thread khác khi chúng có thể bắt đầu hoạt động. Ví dụ, chúng ta cần phải đợi 5 task hoàn thành trước khi các thread khác có thể bắt đầu hoạt động, khi đó starting point của CountDownLatch sẽ là 5. Khi một thread hoàn thành task của nó, nó chỉ có thể gọi method countDown() của CountDownLatch để cho biết một task đã hoàn thành. Method countDown() sẽ giảm starting point, hoặc bộ đếm 1 đơn vị, và khi bộ đếm về 0 CountDownLatch biết rằng tất cả các thread đã kết thúc task của nó và chờ đợi đến lượt các thread khác có thể tiến hành hoạt động ngay.
Method await() là phương thức chặn của CountDownLatch, nó sẽ chặn cho tới khi bộ đếm về 0, sau đó method await() trả về ngay lập tức. Để hiểu rõ hơn chúng ta cùng xem qua ví dụ sau:

import java.util.concurrent.CountDownLatch;
public class CountDownLatchDemo {
    private static final CountDownLatch COUNT_DOWN_LATCH = new CountDownLatch(5);
    public static void main(String[] args) {
        Thread run1 = new Thread(() -> {
            System.out.println("Thread 1 is working...");
            try {
                Thread.sleep(5000);
                COUNT_DOWN_LATCH.countDown();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println("Thread 1 - Finished");
        });
        Thread run2 = new Thread(() -> {
            System.out.println("Thread 2 is working...");
            try {
                Thread.sleep(4000);
                COUNT_DOWN_LATCH.countDown();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println("Thread 2 - Finished");
        });
        Thread run3 = new Thread(() -> {
            System.out.println("Thread 3 is working...");
            try {
                Thread.sleep(3000);
                COUNT_DOWN_LATCH.countDown();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println("Thread 3 - Finished");
        });
        Thread run4 = new Thread(() -> {
            System.out.println("Thread 4 is working...");
            try {
                Thread.sleep(2000);
                COUNT_DOWN_LATCH.countDown();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println("Thread 4 - Finished");
        });
        Thread run5 = new Thread(() -> {
            System.out.println("Thread 5 is working...");
            try {
                Thread.sleep(1000);
                COUNT_DOWN_LATCH.countDown();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println("Thread 5 - Finished");
        });
        run1.start();
        run2.start();
        run3.start();
        run4.start();
        run5.start();
        try {
            COUNT_DOWN_LATCH.await();
        } catch (InterruptedException e) {
            //Handle when a thread gets interrupted.
        }
        System.out.println("All tasks have finished..");
    }
}

Trong ví dụ trên, chúng ta đã định nghĩa COUNT_DOWN_LATCH với starting point là 5, có nghĩa là chúng ta đợi cho 5 threads hoàn thành task trước khi chương trình chính được chạy. Khi đó, ở method chính, chúng ta đã start 5 threads, chúng hoạt động và sau đó gọi method countDown() khi hoàn thành các task của chúng.
Chúng ta gọi method await() của CountDownLatch để đợi cho đến khi bộ đếm về 0, khi đó flow chính của chương trình sẽ bắt đầu hoạt động.
Kết quả sau khi chạy chương trình trên:

Thread 1 is working...
Thread 3 is working...
Thread 4 is working...
Thread 2 is working...
Thread 5 is working...
Thread 5 - Finished
Thread 4 - Finished
Thread 3 - Finished
Thread 2 - Finished
Thread 1 - Finished
All tasks have finished..

Như kết quả, các thread được chạy song song và hoàn thành tại các thời điểm khác nhau, miễn là khi bộ đếm về 0 thì chương trình mới tiếp tục chạy.
Bây giờ chúng ta thử sửa starting point về 4 như sau:
private static final CountDownLatch COUNT_DOWN_LATCH = new CountDownLatch(4);
Kết quả nhận được:

Thread 1 is working...
Thread 2 is working...
Thread 3 is working...
Thread 4 is working...
Thread 5 is working...
Thread 5 - Finished
Thread 4 - Finished
Thread 3 - Finished
Thread 2 - Finished
All tasks have finished..
Thread 1 - Finished

Khi đưa starting point về 4, có nghĩa CountDownLatch chỉ chờ 4 thread hoàn thành, và khi 4 thread hoàn thành coi như bộ đếm count về 0. Khi đó chương trình chính được chạy. (Không cần biết 1 task nữa vẫn đang chưa hoàn thành).
Trong trường hợp sửa starting point về 6 ta sẽ thu được kết quả như sau:

Thread 1 is working...
Thread 2 is working...
Thread 3 is working...
Thread 4 is working...
Thread 5 is working...
Thread 5 - Finished
Thread 4 - Finished
Thread 3 - Finished
Thread 2 - Finished
Thread 1 - Finished

// Program still running
Khi đưa bộ đếm lên 6, thu được kết quả là 5 thread hoàn thành sau đó chương trình vẫn chạy, không hiện ra dòng log All tasks have finished.. bởi vì chương trình chờ 6 task hoàn thành, có nghĩa là 5 thread hoàn thành bộ đếm vẫn ở 1. Và chương trình vẫn đang chờ thêm 1 thread nữa để count về 0. Không có task finish đồng nghĩa với việc bộ đếm chưa về 0 -> flow chính vẫn chưa được chạy.

Kết luận

Bài viết được tham khảo từ "Java Concurrency: Count Down Latches" trình bày về cách sử dụng và hoạt động của Count Down Latches trong Java. Hy vọng bài viết sẽ giúp ích cho các bạn trong quá trình học tập và công việc.