【并发编程】CountDownLatch、CyclicBarrier、Semaphore和Exchanger

81 阅读3分钟

并发编程有一些工具类有CountDownLatch、CyclicBarrier、Semaphore和Exchanger等。

CountDownLatch

  • CountDownLatch允许一个或多个线程等待其他线程完成操作。它内部维护一个计数器,每次调用countDown()方法时计数器减一,当计数器到达零时,等待的线程被唤醒继续执行。
  • 它适用于确保某个活动直到其他某些活动都完成后再继续进行。例如,在并行计算中,主线程需要等待所有工作线程都完成计算后才能继续处理结果。

使用场景

public static void main(String[] args) throws InterruptedException {
    // 创建一个计数器,计数器的初始值为3
    CountDownLatch latch = new CountDownLatch(3);
    
    // 创建三个工作线程
    for (int i = 0; i < 3; i++) {
        final int threadId = i;
        new Thread(()->{
            try {
                System.out.println("线程"+threadId+"正在准备中。。。");
                Thread.sleep((long) (Math.random()*1000));
                System.out.println("线程"+threadId+"准备完毕,等待其他线程...");
                latch.countDown();
            } catch (InterruptedException e) {
                throw new RuntimeException(e);
            }
        }).start();
    }
    // 主线程等待其他三个线程完成准备工作
    latch.await();
    System.out.println("所有线程准备完毕,主线程继续执行...");
    
}

CyclicBarrier

  • CyclicBarrier是一个可重用的屏障,它允许一组线程互相等待,直到所有线程都到达某个屏障点才继续执行。
  • 它适用于并行迭代计算,每个线程处理一部分数据,当所有线程都完成处理后,它们在屏障点汇合,然后开始下一轮迭代。
  • CyclicBarrier可以在所有线程到达屏障点后,执行一个预定义的Runnable命令。

使用场景

public static void main(String[] args) {
        // 创建一个CyclicBarrier实例,当到达屏障的线程数量为3时,执行一个预先定义的任务
        CyclicBarrier barrier = new CyclicBarrier(3, () -> {
            System.out.println("所有线程都到达屏障点,开始执行预定义任务...");
        });
        // 创建三个工作线程
        for (int i = 0; i < 3; i++) {
            final int threadId = i;
            new Thread(() -> {
                try {
                    System.out.println("线程" + threadId + "正在执行任务...");
                    Thread.sleep((long) (Math.random() * 1000)); // 模拟线程执行任务
                    System.out.println("线程" + threadId + "到达屏障点,等待其他线程...");
                    barrier.await(); // 等待其他线程到达屏障点
                    System.out.println("线程" + threadId + "继续执行...");
                } catch (InterruptedException | BrokenBarrierException e) {
                    e.printStackTrace();
                }
            }).start();
        }
 }

执行顺序是当所有线程都到达屏障点的时候,限制性预定义任务,然后再继续执行。

Semaphore

  • Semaphore是一个计数信号量,它维护了一组许可(permit),线程可以通过acquire()获取许可,通过release()释放许可。
  • 如果没有可用的许可,acquire()将阻塞直到有许可被释放。
  • 它适用于限制对某个特定资源的并发访问数量,例如,限制对数据库连接池的连接数量。

使用场景

    public static void main(String[] args) {
        // 创建一个Semaphore实例,表示可用的数据库连接数量
        Semaphore semaphore = new Semaphore(3);
        // 创建多个线程模拟数据库连接请求
        for (int i = 0; i < 10; i++) {
            new Thread(() -> {
                try {
                    // 获取数据库连接
                    semaphore.acquire();
                    System.out.println(Thread.currentThread().getName() + " 获取到数据库连接,开始处理请求...");
                    // 模拟数据库操作
                    Thread.sleep((long) (Math.random() * 1000));
                    // 释放数据库连接
                    System.out.println(Thread.currentThread().getName() + " 处理请求完毕,释放数据库连接");
                    semaphore.release();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }).start();
        }
    }

Exchanger

  • Exchanger是一个用于线程间交换数据的同步点。它允许在并发线程中同步交换数据。
  • 当两个线程都到达交换点时,它们交换数据结构,然后继续执行。
  • 它适用于遗传算法、流水线设计等场景,其中数据需要在两个线程间来回传递。

使用场景

    public static void main(String[] args) {
        // 创建一个Exchanger实例
        Exchanger<String> exchanger = new Exchanger<>();
        // 创建两个线程,模拟数据交换
        new Thread(() -> {
            String data1 = "数据A";
            try {
                System.out.println(Thread.currentThread().getName() + " 正在交换数据: " + data1);
                String data2 = exchanger.exchange(data1);
                System.out.println(Thread.currentThread().Name() + " 收到交换后的数据: " + data2);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }).start();
        new Thread(() -> {
            String data1 = "数据B";
            try {
                System.out.println(Thread.currentThread().getName() + " 正在交换数据: " + data1);
                String data2 = exchanger.exchange(data1);
                System.out.println(Thread.currentThread().getName() + " 收到交换后的数据: " + data2);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }).start();
    }