三个常见JUC辅助类
1. 减少计数(CountDownLatch)
通过一个计数器来管理需要等待的线程数量,当这些线程都完成各自的任务后(即计数器递减到0),才会允许其他等待的线程继续执行。
步骤:
- 定义 CountDownLatch类,并设置一个固定值
- 在需要计数的位置加上 countDown() 方法
- 使用 await() 方法控制计数到达0时的后续步骤
private static void countDownLatchDemo() throws InterruptedException {
CountDownLatch count = new CountDownLatch(NUM);
for (int i = 1; i <= 7; i++) {
new Thread(() -> {
System.out.println(Thread.currentThread().getName() + "同学走出教室");
count.countDown();
}, String.valueOf(i)).start();
}
//count减为0,执行await后面内容
count.await();
System.out.println("班长锁门!");
}
特点与注意事项:
- CountDownLatch的计数器只能减少到0,一旦减少到0后,无法重置计数器。因此,CountDownLatch只能使用一次。
- 一旦计数器的值减少到0,等待线程将被释放,无法再次等待。因此,在使用CountDownLatch时需要注意其这一特性。
- CountDownLatch可以根据实际需求设置等待的线程数量,提供了灵活的线程同步与协调机制。
2. 循环栅栏(CyclicBarrier)
允许一组线程互相等待,直到所有线程都到达一个公共屏障点。在继续执行之前,所有线程都会被阻塞在这个屏障点上。
步骤:
- 定义CyclicBarrier类,指定循环到达的阈值及到达阈值后执行的内容
- 在需要判断阈值的地方调用 await() 方法
private static void cyclicBarrierDemo() {
//定义循环栅栏类 param1(指定的数量) param2(到达数量后执行的内容)
CyclicBarrier barrier = new CyclicBarrier(NUM, () ->{
System.out.println("一起去救爷爷!");
});
for (int i = 1; i <= 7; i++) {
new Thread(() ->{
System.out.println(Thread.currentThread().getName() + "娃诞生");
try {
barrier.await();
} catch (InterruptedException e) {
throw new RuntimeException(e);
} catch (BrokenBarrierException e) {
throw new RuntimeException(e);
}
}, String.valueOf(i)).start();
}
}
特点:
- 线程同步:它提供了一种机制,使得多个线程能够在某个点上同步它们的执行。这对于需要并行处理多个任务,并在所有任务都完成后才进行下一步操作的场景非常有用。
- 结果合并或后续处理:在屏障点,可以指定一个可选的屏障动作(barrier action),当所有线程都到达屏障点时,这个屏障动作会被执行。这通常用于合并各个线程的结果,或者进行一些后续的准备工作。
- 可重用性:
CyclicBarrier
是可重用的,这意味着一旦所有线程都通过了屏障点,并且屏障动作(如果有的话)被执行完毕,CyclicBarrier
会自动重置,以便下一轮线程可以继续使用它进行同步。 - 线程管理:它提供了一种方式来管理参与同步的线程数量,如果某个线程因为某些原因(如中断或超时)未能到达屏障点,那么
CyclicBarrier
可以提供一个机制来处理这种情况,比如通过抛出异常来通知其他线程。
CountDownLatch 和 CyclicBarrier 的区别
总结以下两点:
-
使用场景:
CountDownLatch 更适用于需要等待一组线程完成某个操作的场景
CyclicBarrier 更适用于需要多线程并行处理然后合并结果的场景
-
循环特性:
CountDownLatch 是一个一次性工具,计数器一旦变为0就不能再被重置
CyclicBarrier 允许循环等待,即当一组线程通过屏障点后,可以继续等待下一组线程到达屏障点
3. 信号灯(Semaphore)
用于控制对共享资源的访问数量
步骤:
- 定义 Semaphore 类,设置可同时拿到许可的数量
- 使用 **acquire() **方法获取许可,并执行获取许可后的操作
- 使用 release() 方法释放线程
private static void semaphoreDemo() {
Semaphore semaphore = new Semaphore(3);
for (int i = 1; i <= 7; i++) {
new Thread(() ->{
try {
//获取许可
semaphore.acquire();
System.out.println(Thread.currentThread().getName() + "车抢到了车位!");
//假定停车时间
TimeUnit.SECONDS.sleep(new Random().nextInt(3));
System.out.println(Thread.currentThread().getName() + "车离开了");
} catch (InterruptedException e) {
throw new RuntimeException(e);
} finally {
semaphore.release();
}
}, String.valueOf(i)).start();
}
}
特点:
- 限制并发数:Semaphore通过一个计数器来跟踪当前可用的许可数量,从而限制能够同时访问某个特定资源的线程数量。当线程数量达到Semaphore设定的上限时,额外的线程将会被阻塞,直到有线程释放许可。
- 资源保护:在多线程环境下,多个线程可能同时尝试访问或修改同一个资源,这可能导致数据不一致或资源损坏。Semaphore通过限制同时访问资源的线程数量,可以保护资源免受并发访问的干扰。
- 协调线程:Semaphore提供了一种机制,使得线程之间可以相互协调,以确保它们按照预期的顺序或数量访问共享资源。这有助于避免竞争条件和死锁等问题。
- 实现公平性:Semaphore可以选择性地支持公平性策略,这意味着线程将按照它们请求许可的顺序来获得许可。这有助于避免线程饥饿问题,即某个线程长时间无法获得所需资源的情况。