返回

三大武器 助你玩转并发编程

后端

并发编程中的协调机制:CountDownLatch、CyclicBarrier 和 Semaphore

引言

在多线程编程中,协调线程的执行至关重要。Java 为我们提供了三个强大的辅助类:CountDownLatch、CyclicBarrier 和 Semaphore,它们可以帮助我们简化线程之间的通信和同步。

CountDownLatch:等待所有线程完成

CountDownLatch 是一种等待机制,它允许一个线程等待其他多个线程完成执行后才继续执行。就像一个倒计时器,CountDownLatch 初始化时会指定一个计数,表示需要等待的线程数量。每个线程完成任务后,调用 CountDownLatch 的 countDown() 方法递减这个计数。当计数减到 0 时,等待的线程就会继续执行。

CountDownLatch latch = new CountDownLatch(3);

// 启动 3 个线程
for (int i = 0; i < 3; i++) {
    new Thread(() -> {
        // 执行任务
        latch.countDown();
    }).start();
}

// 等待 3 个线程完成
latch.await();

// 继续执行主线程

CyclicBarrier:等待所有线程到达屏障

CyclicBarrier 类似于 CountDownLatch,但它允许线程在到达某个屏障后继续执行,而不需要等待其他线程完成执行。与 CountDownLatch 不同,CyclicBarrier 可以被多次重置,允许线程在多个屏障之间协调。

CyclicBarrier barrier = new CyclicBarrier(3);

// 启动 3 个线程
for (int i = 0; i < 3; i++) {
    new Thread(() -> {
        // 执行任务
        barrier.await();
    }).start();
}

// 等待 3 个线程到达屏障
barrier.await();

// 继续执行主线程

Semaphore:控制共享资源访问

Semaphore 是一种用于控制同时访问共享资源的线程数量的机制。它允许我们指定一个许可证数量,表示可以同时访问共享资源的线程数量。线程需要访问共享资源时,调用 Semaphore 的 acquire() 方法获取许可证。如果许可证不足,线程会阻塞等待,直到有许可证可用。当线程访问共享资源完毕后,调用 Semaphore 的 release() 方法释放许可证。

Semaphore semaphore = new Semaphore(3);

// 启动 10 个线程
for (int i = 0; i < 10; i++) {
    new Thread(() -> {
        // 尝试获取许可证
        semaphore.acquire();

        // 访问共享资源

        // 释放许可证
        semaphore.release();
    }).start();
}

比较

特性 CountDownLatch CyclicBarrier Semaphore
用途 等待所有线程完成 等待所有线程到达屏障 控制共享资源访问
重置 不可重置 可重置 不可重置
线程状态 阻塞 阻塞 阻塞

结论

CountDownLatch、CyclicBarrier 和 Semaphore 是并发编程中不可或缺的工具,它们可以帮助我们协调线程的执行,防止数据竞争,并确保线程安全的代码。熟练掌握这些辅助类的用法将极大地提高我们的并发编程能力。

常见问题解答

  1. CountDownLatch 和 CyclicBarrier 的区别是什么?
    CountDownLatch 用于等待所有线程完成执行,而 CyclicBarrier 用于等待所有线程到达某个屏障。

  2. Semaphore 有什么特殊用途?
    Semaphore 用于控制同时访问共享资源的线程数量,防止数据竞争。

  3. 如何防止使用 CountDownLatch 时发生死锁?
    确保所有线程最终都会调用 countDown() 方法,以避免死锁。

  4. CyclicBarrier 可以多次使用吗?
    是的,CyclicBarrier 可以被多次重置,允许线程在多个屏障之间协调。

  5. Semaphore 与锁有什么区别?
    Semaphore 是基于许可证的机制,而锁是基于排他访问的机制。Semaphore 允许多个线程同时访问共享资源,只要许可证足够,而锁只允许一个线程在同一时间访问共享资源。