Java 并发工具类:CountDownLatch、CyclicBarrier、Semaphore 详解
一、引言
在 Java 的并发编程中,为了更方便地协调多个线程的执行,java.util.concurrent
包提供了一些实用的并发工具类。其中,CountDownLatch
、CyclicBarrier
和 Semaphore
是三个常用的工具类,它们各自有着独特的功能和应用场景。
二、CountDownLatch
2.1 简介
CountDownLatch
是一个同步辅助类,它允许一个或多个线程等待其他线程完成操作。其内部维护了一个计数器,该计数器的初始值由构造函数指定,每当一个线程完成任务后,会调用 countDown()
方法将计数器减 1,当计数器的值变为 0 时,等待在 await()
方法上的线程将被唤醒继续执行。
2.2 主要方法
CountDownLatch(int count)
:构造函数,初始化计数器的值。void countDown()
:将计数器的值减 1,当计数器变为 0 时,释放所有等待的线程。void await()
:使当前线程等待,直到计数器的值变为 0。boolean await(long timeout, TimeUnit unit)
:使当前线程等待,直到计数器的值变为 0 或等待超时。
2.3 示例代码
import java.util.concurrent.CountDownLatch;
public class CountDownLatchExample {
public static void main(String[] args) throws InterruptedException {
int workerCount = 3;
CountDownLatch latch = new CountDownLatch(workerCount);
for (int i = 0; i < workerCount; i++) {
final int index = i;
new Thread(() -> {
try {
System.out.println("Worker " + index + " 开始工作");
Thread.sleep((long) (Math.random() * 1000));
System.out.println("Worker " + index + " 完成工作");
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
latch.countDown();
}
}).start();
}
System.out.println("主线程等待所有工作线程完成...");
latch.await();
System.out.println("所有工作线程已完成,主线程继续执行");
}
}
2.4 应用场景
- 并行任务的汇总:多个线程同时执行不同的子任务,主线程需要等待所有子任务完成后进行汇总操作。
- 资源初始化:多个线程负责不同资源的初始化,主线程需要等待所有资源初始化完成后才能继续执行后续操作。
三、CyclicBarrier
3.1 简介
CyclicBarrier
是一个同步辅助类,它允许一组线程相互等待,直到所有线程都到达某个公共屏障点(barrier point)。与 CountDownLatch
不同的是,CyclicBarrier
可以被重复使用,当所有线程都到达屏障点后,计数器会被重置,可以继续进行下一轮的等待。
3.2 主要方法
CyclicBarrier(int parties)
:构造函数,指定参与等待的线程数量。CyclicBarrier(int parties, Runnable barrierAction)
:构造函数,除了指定参与等待的线程数量外,还可以指定一个在所有线程到达屏障点后执行的任务。int await()
:使当前线程等待,直到所有线程都到达屏障点。返回值为当前线程到达屏障点的顺序。int await(long timeout, TimeUnit unit)
:使当前线程等待,直到所有线程都到达屏障点或等待超时。
3.3 示例代码
import java.util.concurrent.BrokenBarrierException;
import java.util.concurrent.CyclicBarrier;
public class CyclicBarrierExample {
public static void main(String[] args) {
int playerCount = 3;
CyclicBarrier barrier = new CyclicBarrier(playerCount, () -> {
System.out.println("所有玩家已准备好,游戏开始!");
});
for (int i = 0; i < playerCount; i++) {
final int index = i;
new Thread(() -> {
try {
System.out.println("玩家 " + index + " 正在准备...");
Thread.sleep((long) (Math.random() * 1000));
System.out.println("玩家 " + index + " 已准备好");
barrier.await();
} catch (InterruptedException | BrokenBarrierException e) {
e.printStackTrace();
}
}).start();
}
}
}
3.4 应用场景
- 多线程并行计算:多个线程分别计算不同的数据块,当所有线程都完成计算后,再进行汇总和合并操作。
- 游戏场景:多个玩家需要都准备好后才能开始游戏。
四、Semaphore
4.1 简介
Semaphore
是一个计数信号量,它用于控制同时访问某个资源的线程数量。Semaphore
内部维护了一个许可证(permit)的计数器,线程在访问资源前需要先获取许可证,如果许可证数量大于 0,则线程可以获取许可证并继续执行,同时许可证数量减 1;当线程使用完资源后,需要释放许可证,许可证数量加 1。
4.2 主要方法
Semaphore(int permits)
:构造函数,初始化许可证的数量。Semaphore(int permits, boolean fair)
:构造函数,初始化许可证的数量,并指定是否使用公平锁。void acquire()
:获取一个许可证,如果没有可用的许可证,则当前线程会被阻塞。void acquire(int permits)
:获取指定数量的许可证,如果没有足够的许可证,则当前线程会被阻塞。void release()
:释放一个许可证。void release(int permits)
:释放指定数量的许可证。
4.3 示例代码
import java.util.concurrent.Semaphore;
public class SemaphoreExample {
public static void main(String[] args) {
int resourceCount = 2;
Semaphore semaphore = new Semaphore(resourceCount);
for (int i = 0; i < 5; i++) {
final int index = i;
new Thread(() -> {
try {
semaphore.acquire();
System.out.println("线程 " + index + " 获得资源,开始使用");
Thread.sleep((long) (Math.random() * 1000));
System.out.println("线程 " + index + " 使用完资源,释放资源");
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
semaphore.release();
}
}).start();
}
}
}
4.4 应用场景
- 资源限流:控制同时访问某个资源的线程数量,防止资源被过度使用。
- 数据库连接池:限制同时使用数据库连接的线程数量。
五、总结
CountDownLatch
、CyclicBarrier
和 Semaphore
是 Java 并发编程中非常实用的工具类,它们各自有着不同的功能和应用场景。CountDownLatch
适用于一个或多个线程等待其他线程完成任务的场景;CyclicBarrier
适用于一组线程相互等待到达某个公共屏障点的场景;Semaphore
适用于控制同时访问某个资源的线程数量的场景。合理使用这些工具类可以帮助我们更方便地编写高效、安全的并发程序。