文章快速指引
一、CompletableFuture
1.使用场景
在一次请求多个接口时,需要获取多个接口的返回数据,并进行组装结果集返回。
2.示例代码
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.ExecutionException;
public class CompletableFutureExample {
public static void main(String[] args) throws ExecutionException, InterruptedException {
CompletableFuture<String> future1 = CompletableFuture.supplyAsync(() -> "Task 1");
CompletableFuture<String> future2 = CompletableFuture.supplyAsync(() -> "Task 2");
CompletableFuture<String> future3 = CompletableFuture.supplyAsync(() -> "Task 3");
// 使用allOf和anyOf来等待所有任务完成或任意一个任务完成。
// 等待所有任务完成
CompletableFuture.allOf(future1, future2, future3).join();
// 或者等待任意一个任务完成
CompletableFuture<Void> anyOf = CompletableFuture.anyOf(future1, future2, future3);
if (anyOf.isDone()) {
System.out.println("At least one task is done.");
}
}
}
二、CountDownLatch
1.使用场景
在某些业务场景中,程序执行需要等待某个条件完成后才能继续执行后续的操作。典型的应用为并行计算:当某个处理的运算量很大时,可以将该运算任务拆分成多个子任务,等待所有的子任务都完成之后,父任务再拿到所有子任务的运算结果进行汇总。
2.代码示例
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.TimeUnit;
public class CountDownLatchExample {
public static void main(String[] args) {
ExecutorService exec = Executors.newCachedThreadPool();
final int numThreads = 3;
CountDownLatch latch = new CountDownLatch(numThreads);
for (int i = 0; i < numThreads; i++) {
//这里也可以采用异步的方式进行调用,如@Async注解对应的方法
exec.execute(() -> {
try {
test(threadNum);
} catch (InterruptedException e) {
e.printStackTrace();
}finally {
//每个线程执行结束后,计数器减1
countDownLatch.countDown();
}
});
}
// 主线程等待所有子线程完成
try {
System.out.println("主线程正在等待其他线程完成...");
// 等待所有的线程执行完毕
latch.await();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
private static void doWork(CountDownLatch latch) throws InterruptedException {
System.out.println(Thread.currentThread().getName() + " 开始工作...");
Thread.sleep(2000); // 模拟耗时操作
System.out.println(Thread.currentThread().getName() + " 工作完成。");
// 完成工作后计数器每次减1
latch.countDown();
}
}
CountDownLatch是Java并发包java.util.concurrent中的一种同步辅助类,它允许一个或多个线程等待其他线程完成操作。
CountDownLatch的计数器被初始化为一个特定的值,当计数器到达零时,所有因等待计数器变为零而被阻塞的线程将被释放。
三、Semaphore
1.使用场景
Semaphore常用于仅能提供有限访问的资源,比如:数据库连接数。
2.代码示例
import java.util.concurrent.Semaphore;
/**
下面是一个使用Semaphore的示例,我们将模拟一个停车场场景,停车场只有5个停车位,
当有车辆试图进入时,如果没有空位,则车辆需要等待,直到有车位空出。
*/
public class SemaphoreExample {
public static void main(String[] args) {
final int NUM_PARKING_SPOTS = 5;
// 初始化5个许可
Semaphore parkingLot = new Semaphore(NUM_PARKING_SPOTS, true);
for (int i = 0; i < 10; i++) {
new Thread(() -> {
try {
parkCar(parkingLot);
} catch (InterruptedException e) {
e.printStackTrace();
}
}).start();
}
}
private static void parkCar(Semaphore parkingLot) throws InterruptedException {
System.out.println(Thread.currentThread().getName() + " 正在寻找停车位...");
// 尝试获取一个停车许可
parkingLot.acquire();
System.out.println(Thread.currentThread().getName() + " 找到了停车位。");
try {
// 模拟停车时间
Thread.sleep(2000);
System.out.println(Thread.currentThread().getName() + " 停车结束。");
} finally {
// 释放停车许可
parkingLot.release();
System.out.println(Thread.currentThread().getName() + " 离开了停车场。");
}
}
}
Semaphore是Java并发包java.util.concurrent中用于控制对共享资源访问的同步工具类。它通过一组许可(permits)来控制对资源的访问,当许可数量大于0时,线程可以获取许可并访问资源,否则线程将被阻塞直到获得许可。
四、CyclicBarrier
1.使用场景
可以用于多线程计算数据,最后合并计算结果的场景
2.代码示例
import java.util.concurrent.CyclicBarrier;
import java.util.concurrent.BrokenBarrierException;
/**
下面是一个使用CyclicBarrier的示例,我们将模拟一个比赛场景,其中多个赛跑者(线程)在起跑线上等待裁判的发令枪声,所有赛跑者同时开始跑步。
*/
public class CyclicBarrierExample {
public static void main(String[] args) {
final int numberOfRunners = 5;
CyclicBarrier barrier = new CyclicBarrier(numberOfRunners, () -> {
System.out.println("所有赛跑者已经准备就绪,比赛开始!");
});
for (int i = 0; i < numberOfRunners; i++) {
new Thread(() -> {
try {
prepareRunner(barrier);
} catch (InterruptedException | BrokenBarrierException e) {
e.printStackTrace();
}
}).start();
}
}
private static void prepareRunner(CyclicBarrier barrier) throws InterruptedException, BrokenBarrierException {
System.out.println(Thread.currentThread().getName() + " 正在准备...");
// 模拟准备时间
Thread.sleep((long) (Math.random() * 1000));
System.out.println(Thread.currentThread().getName() + " 准备完毕,等待发令枪响。");
// 等待所有赛跑者准备就绪
barrier.await();
System.out.println(Thread.currentThread().getName() + " 开始跑步!");
// 模拟跑步时间
Thread.sleep((long) (Math.random() * 1000));
System.out.println(Thread.currentThread().getName() + " 到达终点!");
}
}
CyclicBarrier是Java并发包java.util.concurrent中的一个同步辅助类,它允许一组线程相互等待,直到到达某个公共屏障点(barrier)。当所有参与的线程都到达了这个屏障点,所有线程将被释放并继续执行。CyclicBarrier的一个关键特性是它是可重用的,即一旦释放了所有线程,它可以再次被重置以供下一轮循环使用。
四、CyclicBarrier与CountDownLatch的区别
- CountDownLatch的计数器只能使用一次,而CyclicBarrier的计数器可以使用reset()方法进行重置,并且可以循环使用
- CountDownLatch主要实现1个或n个线程需要等待其他线程完成某项操作之后,才能继续往下执行,描述的是1个或n个线程等待其他线程的关系。而CyclicBarrier主要实现了多个线程之间相互等待,直到所有的线程都满足了条件之后,才能继续执行后
续的操作,描述的是各个线程内部相互等待的关系。 - CyclicBarrier能够处理更复杂的场景,如果计算发生错误,可以重置计数器让线程重新执行一次。
- CyclicBarrier中提供了很多有用的方法,比如:可以通过getNumberWaiting()方法获取阻塞的线程数量,通过isBroken()方法判断阻塞的线程是否被中断。