CountDownLatch 和 CyclicBarrier 使用场景详解

CountDownLatch 和 CyclicBarrier 使用场景详解

在并发编程中,Java 提供了多种工具来帮助管理线程之间的协调。CountDownLatchCyclicBarrier 是两种常用的同步工具类,它们虽然功能不同,但都用于线程之间的协调与同步。


1. CountDownLatch 使用场景

CountDownLatch 是一个线程同步工具,它允许一个或多个线程等待其他线程完成一组操作。这种机制非常适用于以下场景:

  • 并行任务的等待:当你有多个子任务需要并行执行,并且在所有子任务完成后再继续执行主任务时,CountDownLatch 是理想的选择。
  • 一次性事件触发:某些场景下,你可能需要在特定数量的操作完成后触发某个事件,比如多线程的初始化工作。
代码示例:并行任务的等待
import java.util.concurrent.CountDownLatch;

public class CountDownLatchExample {
    private static final int TASK_COUNT = 3;
    
    public static void main(String[] args) throws InterruptedException {
        CountDownLatch latch = new CountDownLatch(TASK_COUNT);

        for (int i = 0; i < TASK_COUNT; i++) {
            new Thread(new Task(latch)).start();
        }

        // 等待所有任务完成
        latch.await();
        System.out.println("所有任务已完成,继续主线程工作");
    }

    static class Task implements Runnable {
        private final CountDownLatch latch;

        Task(CountDownLatch latch) {
            this.latch = latch;
        }

        @Override
        public void run() {
            System.out.println(Thread.currentThread().getName() + " 正在执行任务...");
            // 模拟任务耗时操作
            try {
                Thread.sleep(2000);
            } catch (InterruptedException e) {
                Thread.currentThread().interrupt();
            }
            System.out.println(Thread.currentThread().getName() + " 任务完成");
            latch.countDown(); // 每个线程完成后将计数器减1
        }
    }
}
源码分析

CountDownLatch 的核心在于它维护了一个计数器,该计数器在初始化时设置为指定的数量。每当一个线程完成任务后,会调用 countDown() 方法将计数器减1。当计数器归零时,所有在 await() 方法上等待的线程将继续执行。

public void countDown() {
    sync.releaseShared(1);
}

public void await() throws InterruptedException {
    sync.acquireSharedInterruptibly(1);
}
  • countDown() 方法:通过 sync.releaseShared(1) 来递减计数器。
  • await() 方法:调用 sync.acquireSharedInterruptibly(1) 来阻塞当前线程,直到计数器为零。
场景还原
场景一

假设你有一个任务需要从多个数据源中获取数据,并在所有数据都获取到之后进行处理。在这种场景下,CountDownLatch 可以确保主线程在所有数据获取完毕之后再开始处理。

场景二:大数据处理中的分布式计算同步

在大数据处理场景中,通常需要将一个大任务分解为多个小任务,并分布在多个节点上进行计算。每个计算节点在完成自己的部分任务后,都需要等待其他节点完成,然后进行数据的聚合。CyclicBarrier 可以用于这种场景,确保所有节点都完成任务后再继续下一步的聚合处理。

逻辑图:

2. CyclicBarrier 使用场景

CyclicBarrier 也是一个线程同步工具,它允许一组线程相互等待,直到所有线程都到达一个共同的屏障点。与 CountDownLatch 不同,CyclicBarrier 可以在屏障点被释放后再次使用,因此适用于多次使用的场景。

  • 分段任务处理:当任务被分成多个阶段,每个阶段都需要所有线程完成后才能继续下一个阶段时,CyclicBarrier 是最佳选择。
  • 多线程模拟:在模拟多线程同时开始的场景中,比如多线程并发测试。
代码示例:分段任务处理
import java.util.concurrent.BrokenBarrierException;
import java.util.concurrent.CyclicBarrier;

public class CyclicBarrierExample {
    private static final int THREAD_COUNT = 3;
    private static CyclicBarrier barrier = new CyclicBarrier(THREAD_COUNT, () -> {
        System.out.println("所有任务准备完毕,开始执行下一阶段");
    });

    public static void main(String[] args) {
        for (int i = 0; i < THREAD_COUNT; i++) {
            new Thread(new Task()).start();
        }
    }

    static class Task implements Runnable {
        @Override
        public void run() {
            try {
                System.out.println(Thread.currentThread().getName() + " 正在执行第一阶段...");
                Thread.sleep(1000); // 模拟第一阶段任务
                barrier.await(); // 等待其他线程完成第一阶段

                System.out.println(Thread.currentThread().getName() + " 正在执行第二阶段...");
                Thread.sleep(1000); // 模拟第二阶段任务
                barrier.await(); // 等待其他线程完成第二阶段
            } catch (InterruptedException | BrokenBarrierException e) {
                Thread.currentThread().interrupt();
            }
        }
    }
}
源码分析

CyclicBarrier 的内部维护了一个计数器,当所有参与线程都调用 await() 方法并达到屏障点时,计数器归零,并触发 Runnable 任务。

public int await() throws InterruptedException, BrokenBarrierException {
    return dowait(false, 0L);
}

private int dowait(boolean timed, long nanos) throws InterruptedException, BrokenBarrierException, TimeoutException {
    final ReentrantLock lock = this.lock;
    lock.lock();
    try {
        // 计数器减1,直到为0
        final Generation g = generation;
        final int index = --count;
        if (index == 0) {  // 所有线程都已到达
            nextGeneration();  // 进入下一代
            return 0;
        }
        ...
    } finally {
        lock.unlock();
    }
}
  • await() 方法:通过 dowait 方法实现线程的同步等待,并在计数器归零后重置屏障,进入下一代。
  • nextGeneration() 方法:重置 countgeneration,使得屏障可以重新使用。
场景还原
场景一

假设你在开发一个复杂的算法,其分为多个步骤,每个步骤都需要多个线程协同工作并在同一时间点完成。CyclicBarrier 可以确保所有线程都在完成当前步骤后,统一进入下一步骤。

场景二:模拟多人游戏中的回合制同步

在多人在线游戏中,常常需要在回合制游戏中同步所有玩家的行动结果,然后进入下一个回合。例如,多个玩家在同一回合中选择动作,所有玩家的动作都完成后,统一执行这些动作,再进入下一回合。

逻辑图:

   Player1  
      |
(CyclicBarrier.await)
   Player2  
      |
(CyclicBarrier.await)
   Player3  
      |
(CyclicBarrier.await)
     |
+---------------------------+
| 执行本回合的所有玩家动作 |  
+---------------------------+
     |
+---------------------------+
| 准备下一个回合            |  
+---------------------------+
     |
(CyclicBarrier.await)

业务逻辑:

  1. 每个玩家选择动作并准备好后调用 CyclicBarrier.await()
  2. 当所有玩家都准备好后,游戏服务器执行所有玩家的动作。
  3. 进入下一回合,重复上述步骤。
场景三:多线程模拟交易系统的负载测试

在金融交易系统中,经常需要进行负载测试,以模拟高并发情况下系统的表现。假设要模拟多用户同时提交交易请求,可以使用 CyclicBarrier 来同步多个模拟用户,使它们在同一时刻开始交易操作,从而测试系统在高负载下的性能表现。

逻辑图:

   User1  
      |
(CyclicBarrier.await)
   User2  
      |
(CyclicBarrier.await)
   User3  
      |
(CyclicBarrier.await)
     |
+---------------------------+
| 同时提交交易请求          |  
+---------------------------+
     |
+---------------------------+
| 交易系统处理              |  
+---------------------------+

业务逻辑:

  1. 多个线程分别模拟不同用户,准备好交易请求后调用 CyclicBarrier.await()
  2. 所有用户都准备好后,系统在同一时间接收到所有交易请求。
  3. 交易系统处理请求,并记录性能数据,用于分析系统在高负载下的表现。
3. 注意点
  • CountDownLatch 是一次性的,计数器一旦归零便不能重置;而 CyclicBarrier 是可以重复使用的。
  • 在使用 CountDownLatch 时,确保所有 countDown() 调用都能正确执行,否则会导致 await() 永远阻塞。
  • 在使用 CyclicBarrier 时,注意处理 BrokenBarrierException 异常,该异常通常在屏障被破坏时抛出,如有线程中途退出。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值