引言
在Java并发编程的世界里,有这样四位神秘的守护者,它们分别是CountDownLatch
、CyclicBarrier
、Semaphore
和Exchanger
。它们各自拥有独特的魔法,帮助开发者解决各种并发问题。今天,就让我们一起揭开它们的神秘面纱,深入了解它们的运行原理、应用场景以及源码分析。
2024最全大厂面试题无需C币点我下载或者在网页打开全套面试题已打包
AI绘画关于SD,MJ,GPT,SDXL,Comfyui百科全书
夸张风格的标题
《Java并发工具箱:揭秘CountDownLatch、CyclicBarrier、Semaphore、Exchanger的神秘力量!》
CountDownLatch:倒计时的魔法
CountDownLatch
是一个同步辅助类,它允许一个或多个线程等待直到在其他线程中执行的一组操作完成。它就像一个倒计时器,等待计数到达零,然后释放所有等待的线程。
运行原理
CountDownLatch
内部维护了一个计数器,这个计数器的初始值由构造函数指定。每当一个线程调用countDown()
方法时,计数器的值就会减一。当计数器的值减到零时,所有等待的线程都会被唤醒。
应用场景
CountDownLatch
常用于以下场景:
- 多线程等待一组操作完成:例如,多个线程在等待一组数据库操作完成后再继续执行。
- 主线程等待多个子线程完成:在主程序中等待所有子线程执行完毕。
源码分析
public class CountDownLatch {
private final Sync sync;
public CountDownLatch(int count) {
if (count < 0) throw new IllegalArgumentException("count < 0");
this.sync = new Sync(count);
}
public void await() throws InterruptedException {
sync.acquireSharedInterruptibly(1);
}
public void countDown() {
sync.releaseShared(1);
}
// 其他方法...
}
在上述代码中,CountDownLatch
类的构造函数接受一个计数器的初始值。await()
方法用于等待计数器减到零,而countDown()
方法用于减少计数器的值。
CyclicBarrier:循环栅栏的魔力
CyclicBarrier
是一个同步辅助类,它允许一组线程互相等待,直到到达某个公共屏障点。它就像一个循环的栅栏,线程们可以重复地等待和通过。
运行原理
CyclicBarrier
内部维护了一个计数器和一个栅栏操作。当线程调用await()
方法时,计数器的值会增加。当计数器的值达到构造函数中指定的值时,所有等待的线程会被唤醒,并且可以执行栅栏操作。之后,计数器会重置,等待的线程可以再次等待。
应用场景
CyclicBarrier
常用于以下场景:
- 多线程并行计算:在并行计算中,多个线程可以同时执行计算,然后在计算完成后使用
CyclicBarrier
等待其他线程完成。 - 多阶段任务处理:在处理多阶段任务时,每个阶段完成后,线程可以使用
CyclicBarrier
等待其他线程完成当前阶段。
源码分析
public class CyclicBarrier {
private final ReentrantLock lock = new ReentrantLock();
private final Condition trip = lock.newCondition();
private final int parties;
private final Runnable barrierCommand;
private int count;
public CyclicBarrier(int parties, Runnable barrierAction) {
if (parties <= 0) throw new IllegalArgumentException();
this.parties = parties;
this.count = parties;
this.barrierCommand = barrierAction;
}
public int await() throws InterruptedException, BrokenBarrierException {
lock.lock();
try {
int index = --count;
if (index == 0) { // tripped
try {
barrierCommand.run();
} finally {
onReset();
}
trip.signalAll();
return 0;
}
for (;;) { // loop until tripped, broken, interrupted, or timed out
try {
trip.await();
} catch (InterruptedException ie) {
breakBarrier();
throw ie;
} catch (BrokenBarrierException e) {
breakBarrier();
throw e;
}
}
} finally {
lock.unlock();
}
return index;
}
// 其他方法...
}
在上述代码中,CyclicBarrier
类的构造函数接受一个线程数和一个可选的栅栏操作。await()
方法用于等待其他线程到达栅栏点。
Semaphore:信号量的奥秘
Semaphore
是一个计数信号量,它控制同时访问某个特定资源的线程数量。它就像一个守门员,控制着资源的访问权限。
运行原理
Semaphore
内部维护了一个计数器和一个等待队列。当线程调用acquire()
方法时,如果计数器的值大于零,计数器的值会减一,线程可以继续执行。如果计数器的值为零,线程会被放入等待队列中。当线程调用release()
方法时,计数器的值会增加,如果有线程在等待队列中,它们会被唤醒。
应用场景
Semaphore
常用于以下场景:
- 限流控制:控制对某个资源的并发访问数量。
- 资源池管理:管理一组可复用的资源,如数据库连接。
源码分析
public class Semaphore {
private final Sync sync;
public Semaphore(int permits) {
sync = new NonfairSync(permits);
}
public void acquire() throws InterruptedException {
sync.acquireSharedInterruptibly(1);
}
public void release() {
sync.releaseShared(1);
}
// 其他方法...
}
在上述代码中,Semaphore
类的构造函数接受一个许可数。acquire()
方法用于获取许可,而release()
方法用于释放许可。
Exchanger:线程间交换的魔法
Exchanger
是一个用于两个线程之间交换数据的同步点。它就像一个交换站,允许两个线程在某个点交换数据。
运行原理
Exchanger
内部维护了一个队列,用于存储等待交换的线程。当一个线程调用exchange()
方法时,它会将数据放入队列中,并等待另一个线程到达。当另一个线程到达并调用exchange()
方法时,两个线程的数据会被交换,并且两个线程都会被唤醒。
应用场景
Exchanger
常用于以下场景:
- 生产者-消费者模式:生产者和消费者在交换点交换数据。
- 任务协作:两个线程协作完成任务,需要在某个点交换数据。
源码分析
public class Exchanger<V> {
private final Participant participant;
public Exchanger() {
participant = new Participant();
}
public V exchange(V x) throws InterruptedException {
return exchange(x, false, 0L);
}
// 其他方法...
}
在上述代码中,Exchanger
类的构造函数不接受任何参数。exchange()
方法用于交换数据。