详解java CountDownLatch和CyclicBarrier在内部实现和场景上的区别

在多线程编程中,我们常常会用到Java中的并发工具类CountDownLatch和CyclicBarrier,它们都是用于线程同步的一种工具。本文将从内部实现和场景上的区别来详细讲解这两种工具类。

详解Java CountDownLatch和CyclicBarrier

在多线程编程中,我们常常会用到Java中的并发工具类CountDownLatchCyclicBarrier,它们都是用于线程同步的一种工具。本文将从内部实现和场景上的区别来详细讲解这两种工具类。

CountDownLatch

CountDownLatch在多线程中被用于等待一个或多个事件完成后再执行某种操作。它的工作原理是:一个计数器被初始化为某个数值,只要每个事件完成,计数器就会减1。当计数器的值为0时,代表所有事件已完成,此时等待线程可以继续执行。

下面是一个示例,展示了CountDownLatch的使用:

import java.util.concurrent.CountDownLatch;

public class Example {
    public static void main(String[] args) {
        final CountDownLatch latch = new CountDownLatch(2);

        new Thread(() -> {
            try {
                System.out.println("Thread 1 is running");
                Thread.sleep(1000);
                System.out.println("Thread 1 is done");
                latch.countDown();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }).start();

        new Thread(() -> {
            try {
                System.out.println("Thread 2 is running");
                Thread.sleep(2000);
                System.out.println("Thread 2 is done");
                latch.countDown();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }).start();

        try {
            System.out.println("Waiting for threads to finish");
            latch.await();
            System.out.println("All threads are done");
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
}

输出结果如下:

Waiting for threads to finish
Thread 1 is running
Thread 2 is running
Thread 1 is done
Thread 2 is done
All threads are done

在此示例中,我们创建了一个CountDownLatch对象,并将其初始化为2。然后,我们创建了两个线程,让它们执行一些耗时的操作,最后等待线程会在调用await()方法时阻塞,直到两个线程将计数器减为0,然后输出"All threads are done"。

CyclicBarrier

CyclicBarrier也是用于线程同步的一种工具。它跟CountDownLatch最大的区别是:CountDownLatch的计数器在减到0之后就不能再用了,而CyclicBarrier的计数器在减到0之后可以被重置,因此它也被称为“可循环屏障”。

CyclicBarrier的工作原理是:创建一个CyclicBarrier对象时需要指定一个线程数和一个barrier action,当这个线程数的线程全部执行到barrier action之后,这些线程才会继续向下执行。

下面是一个示例,展示了CyclicBarrier的使用:

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

public class Example {
    public static void main(String[] args) {
        final int THREADS_NUM = 3;
        final CyclicBarrier barrier = new CyclicBarrier(THREADS_NUM, () -> {
            System.out.println("All threads are done");
        });

        for (int i = 0; i < THREADS_NUM; i++) {
            new Thread(() -> {
                try {
                    System.out.println(Thread.currentThread().getName() + " is running");
                    Thread.sleep(1000);
                    System.out.println(Thread.currentThread().getName() + " is done");
                    barrier.await();
                } catch (InterruptedException | BrokenBarrierException e) {
                    e.printStackTrace();
                }
            }, "Thread-" + i).start();
        }
    }
}

输出结果如下:

Thread-0 is running
Thread-1 is running
Thread-2 is running
Thread-2 is done
Thread-1 is done
Thread-0 is done
All threads are done

在此示例中,我们创建了一个CyclicBarrier对象,并将其初始化为3,同时创建三个线程。每个线程都会执行一些耗时的操作,并调用await()方法等待其他线程执行完毕。当三个线程都执行到barrier,即计数器减为0时,会执行barrier action,输出"All threads are done"。三个线程输出的顺序不一定是一致的。

区别

  • 内部实现

CountDownLatch采用了AQS队列,共享变量是一个int类型的计数器,每当一个线程完成它的工作后,计数器就会减1。当计数器的值变为0时,所有等待的线程都会被唤醒。而CyclicBarrier使用了ReentrantLock和Condition和AQS队列实现,还有一个parties表示初始化时需要等待的线程个数,这个值保持不变。

  • 应用场景

CountDownLatch通常用于等待异步任务的结果返回,将结果合并后再执行接下来的操作。例如多个线程处理一堆请求,等这些线程全部处理完毕以后再将处理结果进行合并。CyclicBarrier通常用于在多个线程执行过程中,等待其他线程同步完成后再继续执行后面的业务逻辑,例如多个线程写入数据,最后将数据合并在一起并输出。

本文标题为:详解java CountDownLatch和CyclicBarrier在内部实现和场景上的区别

基础教程推荐