JUC 三大辅助类: CountDownLatch CyclicBarrier Semaphore
在 Java 并发编程领域,java.util.concurrent
(JUC)包提供了丰富的工具来帮助开发者处理多线程环境下的复杂问题。其中,CountDownLatch
、CyclicBarrier
和Semaphore
这三大辅助类,在协调线程同步、控制并发访问等方面发挥着重要作用。本文将深入探讨这三个类的原理、使用场景以及具体的代码示例。
CountDownLatch
原理与功能
CountDownLatch
是一个同步辅助类,它允许一个或多个线程等待,直到其他线程完成一组操作。其内部维护了一个计数器,通过countDown()
方法递减计数,当计数器减为 0 时,所有等待的线程将被释放。
代码示例
import java.util.concurrent.CountDownLatch;
public class CountDownLatchExample {
public static void main(String[] args) {
int numThreads = 5;
CountDownLatch latch = new CountDownLatch(numThreads);
for (int i = 0; i < numThreads; i++) {
new Thread(() -> {
System.out.println(Thread.currentThread().getName() + " 开始工作");
try {
Thread.sleep((long) (Math.random() * 1000));
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() + " 工作完成");
latch.countDown();
}).start();
}
try {
System.out.println("主线程等待所有线程完成...");
latch.await();
System.out.println("所有线程已完成,主线程继续执行");
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
在上述代码中,主线程创建了一个CountDownLatch
,初始计数为 5。每个子线程在完成工作后调用countDown()
方法,主线程通过await()
方法等待,直到所有子线程都调用了countDown()
,即计数器为 0,然后主线程继续执行。
使用场景
- 任务编排:在一个复杂的任务中,可能需要多个子任务并行执行,当所有子任务完成后,再进行下一步操作。例如,在一个数据处理系统中,多个线程分别处理不同部分的数据,当所有线程都处理完后,再进行数据汇总。
- 测试场景:在编写多线程测试用例时,可以使用
CountDownLatch
来确保所有线程都到达某个状态后,再进行后续的断言和验证。
CyclicBarrier
原理与功能
CyclicBarrier
也是一个同步辅助类,它允许一组线程相互等待,直到所有线程都到达某个屏障点。与CountDownLatch
不同的是,CyclicBarrier
可以重复使用。当所有线程都到达屏障点后,CyclicBarrier
可以执行一个可选的Runnable
任务,然后所有线程继续执行。
代码示例
import java.util.concurrent.CyclicBarrier;
public class CyclicBarrierExample {
public static void main(String[] args) {
int numThreads = 3;
CyclicBarrier barrier = new CyclicBarrier(numThreads, () -> {
System.out.println("所有线程已到达屏障点,开始下一步操作");
});
for (int i = 0; i < numThreads; i++) {
new Thread(() -> {
System.out.println(Thread.currentThread().getName() + " 开始工作");
try {
Thread.sleep((long) (Math.random() * 1000));
} catch (InterruptedException e) {
e.printStackTrace();
}
try {
System.out.println(Thread.currentThread().getName() + " 到达屏障点");
barrier.await();
} catch (Exception e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() + " 继续工作");
}).start();
}
}
}
在这个示例中,创建了一个CyclicBarrier
,当 3 个线程都调用await()
方法到达屏障点时,会执行传入的Runnable
任务(打印一条消息),然后所有线程继续执行后续操作。
使用场景
- 多阶段任务:在一个任务中,可能存在多个阶段,每个阶段需要所有线程都完成上一阶段的工作后,才能继续进行。例如,在一个分布式计算任务中,每个节点完成局部计算后,需要等待所有节点都完成,然后再进行全局计算。
- 并发控制:通过
CyclicBarrier
可以实现对并发线程的控制,确保在某个时间点,所有线程都处于同一状态,然后再继续执行。
Semaphore
原理与功能
Semaphore
是一个计数信号量,它通过控制一个许可证(permit)的数量来控制对共享资源的并发访问。线程在访问共享资源前,需要先获取一个许可证,如果没有可用的许可证,线程将被阻塞,直到有许可证可用。当线程访问完共享资源后,需要释放许可证。
代码示例
import java.util.concurrent.Semaphore;
public class SemaphoreExample {
public static void main(String[] args) {
int availablePermits = 3;
Semaphore semaphore = new Semaphore(availablePermits);
for (int i = 0; i < 5; i++) {
new Thread(() -> {
try {
System.out.println(Thread.currentThread().getName() + " 尝试获取许可证");
semaphore.acquire();
System.out.println(Thread.currentThread().getName() + " 获取到许可证,开始访问共享资源");
Thread.sleep((long) (Math.random() * 1000));
System.out.println(Thread.currentThread().getName() + " 访问共享资源结束,释放许可证");
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
semaphore.release();
}
}).start();
}
}
}
在上述代码中,创建了一个Semaphore
,初始许可证数量为 3。5 个线程尝试获取许可证,最多只有 3 个线程可以同时获取到许可证并访问共享资源,其他线程将被阻塞,直到有许可证被释放。
使用场景
- 资源控制:在数据库连接池、线程池等场景中,通过
Semaphore
可以控制对资源的并发访问数量,避免资源被过度使用。例如,在数据库连接池,Semaphore
可以控制同时获取数据库连接的线程数量,防止数据库连接数过多导致性能下降。 - 限流:在网络应用中,可以使用
Semaphore
实现限流功能,控制单位时间内访问某个接口的请求数量。
结语
感谢您的阅读!如果您对 JUC 包中的其他组件或并发编程话题有任何疑问或见解,欢迎继续探讨。