当前位置: 首页 > news >正文

java每日精进 11.04【关于线程的思考】

1.线程池的任务分派与执行

线程池、线程和任务单,都是什么?​

咱打个比方,把线程池看作是一家工厂,线程就是工厂里的工人,而Thread、Runnable、Callable这三个,就像是三种不同类型的任务单。这样一比喻,是不是就感觉它们之间的关系一下子清晰多啦!简单来说,线程池这个工厂管理着线程这些工人,而我们要让工人干活,就需要给工厂提交任务单,也就是Thread、Runnable、Callable ,工厂会安排工人去执行任务单上的任务。​

三种任务单,有何不同?​

Runnable 接口:只干活,不汇报​

在 Java 中,Runnable接口是一个比较基础的接口,专门用来定义线程需要执行的任务。这个接口只有一个run方法,当线程执行这个任务的时候,就会调用这个run方法。但它有个特点,就是这个方法没有返回值,也就是说任务执行完了就完了,不会给调用者返回任何结果。​

就好比工厂里,老板给工人一张任务单,上面写着 “打扫车间”,工人接到任务单后就去干活,把车间打扫干净。但打扫完之后,工人不需要跟老板汇报具体打扫了多少垃圾,干完事就直接等着下一个任务了 ,这种只需要单纯执行任务,不需要返回结果的场景,就很适合用Runnable接口。​

Callable 接口:干活后,要汇报​

Callable接口和Runnable接口很相似,也是用来定义任务的。但它比Runnable接口多了些功能,它的call方法有返回值,而且还能抛出异常。这意味着使用Callable定义的任务,在执行完成后可以返回一个结果给调用者,并且在执行过程中如果遇到问题,还能把异常信息传递出来。​

还是以工厂为例,老板给工人一张任务单,上面写着 “清点仓库货物”,工人清点完货物后,必须跟老板汇报仓库里有多少件商品,比如汇报 “有 100 件商品”。这种需要返回任务执行结果的场景,Callable接口就派上用场啦。​

Thread 类:自带工具的特殊任务单​

Thread类比较特殊,它本身就实现了Runnable接口,也就意味着它具备定义任务的能力。当我们创建一个Thread类的实例时,可以重写它的run方法,在里面定义具体的任务逻辑。不过,当把Thread当作任务单提交到线程池时,线程池并不会使用它自带的线程来执行任务,而是忽略它的start方法,只执行run方法里的任务逻辑。​

就好像工人带着自己的工具来干活,任务单上写着具体的干活步骤和自带的工具说明,但工厂有自己统一的工具,所以工厂只采用任务单上的干活步骤,而不用工人自带的工具 。在实际开发中,虽然Thread类也能用来定义任务,但因为线程池更强调线程的复用,所以一般不推荐把Thread类当作任务单提交到线程池,而是优先使用Runnable和Callable接口。​

线程池如何处理任务单?​

线程池就好比是工厂的老板,手下管理着一群固定的工人,也就是线程。不管你提交的是哪种任务单,老板都会安排一个空闲的工人去执行任务,并不会因为任务单的类型不同,就更换执行任务的工人。​

当提交Runnable任务时,就像是老板安排工人去完成 “打扫车间” 这样的任务。工人会执行Runnable接口里的run方法,勤勤恳恳地完成任务后,就接着去执行下一个任务。​

而提交Callable任务时,比如老板安排工人去 “清点仓库货物”。工人执行Callable接口里的call方法,完成任务后,还得把结果汇报给老板,这个结果会通过Future返回给我们,就好像工人把清点的货物数量汇报给老板一样。​

要是提交Thread任务呢,虽然Thread类自带线程,就好比工人自带工具来干活。但线程池这个老板可不允许工人用自己带来的线程,而是忽略Thread的start方法,只让工人执行Thread里run方法中的任务逻辑,也就是只用任务单上的干活步骤,不用自带的工具 。​

任务单能随意组合提交吗?​

当然可以!这三种任务单完全能混合提交给线程池,相互之间不会产生冲突。打个比方,在工厂里,工人的工作安排是很灵活的。上午的时候,老板安排工人去打扫车间,这就相当于提交了一个Runnable任务,工人执行完打扫任务后,不需要汇报具体打扫成果,接着等待下一个任务。​

到了下午,老板又安排工人去清点仓库货物,这就是提交了一个Callable任务,工人清点完货物后,必须把货物数量汇报给老板,也就是返回任务执行结果。​

晚上,老板让工人用自己的一套特殊步骤去打扫仓库,这就类似提交了一个Thread任务,虽然工人自带了工具和步骤,但工厂还是按照自己的安排,只让工人执行任务单上的打扫步骤 。​

从这个例子就能看出,不管是哪种任务单,线程池都能很好地处理,就像工厂老板能灵活安排工人完成各种不同类型的任务一样,完全不冲突。在实际的编程中,我们也可以根据具体的业务需求,灵活地把Runnable、Callable、Thread这三种任务单混合提交给线程池 。​

为了让你更直观地理解,我给你举个代码例子:​

import java.util.concurrent.*;public class ThreadPoolExample {public static void main(String[] args) {// 创建一个有2个工人的工厂(线程池)ExecutorService pool = Executors.newFixedThreadPool(2);// 1. 提交Runnable任务(只干活,不汇报)pool.execute(() -> System.out.println("工人1:打扫车间完成"));// 2. 提交Callable任务(干活后汇报结果)Future<Integer> future = pool.submit(() -> {System.out.println("工人2:清点货物中...");return 100; // 汇报结果:100件});// 3. 提交Thread任务(用它的任务逻辑,不用它的线程)Thread threadTask = new Thread(() -> System.out.println("工人1:用自带步骤打扫仓库完成"));pool.execute(threadTask);// 获取Callable的结果try {System.out.println("老板收到清点结果:" + future.get() + "件");} catch (Exception e) {e.printStackTrace();}pool.shutdown();}
}

在这段代码里,我们创建了一个包含 2 个线程的线程池,就像一个有 2 个工人的工厂。然后分别提交了Runnable、Callable、Thread这三种任务单。线程池会依次安排线程去执行这些任务,最后执行结果如下(工人 1 和工人 2 是线程池里的固定线程):​

plaintext取消自动换行复制

工人1:打扫车间完成​

工人2:清点货物中...​

工人1:用自带步骤打扫仓库完成​

老板收到清点结果:100件​

从结果可以看出,线程池成功地处理了这三种不同类型的任务单,它们之间并没有冲突 。​

代码示例,直观展示​

为了让你更直观地感受这三种任务单在代码里是怎么提交到线程池里执行的,我来给你展示一段 Java 代码:​

import java.util.concurrent.*;public class ThreadPoolExample {public static void main(String[] args) {// 创建一个有2个工人的工厂(线程池)ExecutorService pool = Executors.newFixedThreadPool(2);// 1. 提交Runnable任务(只干活,不汇报)pool.execute(() -> System.out.println("工人1:打扫车间完成"));// 2. 提交Callable任务(干活后汇报结果)Future<Integer> future = pool.submit(() -> {System.out.println("工人2:清点货物中...");return 100; // 汇报结果:100件});// 3. 提交Thread任务(用它的任务逻辑,不用它的线程)Thread threadTask = new Thread(() -> System.out.println("工人1:用自带步骤打扫仓库完成"));pool.execute(threadTask);// 获取Callable的结果try {System.out.println("老板收到清点结果:" + future.get() + "件");} catch (Exception e) {e.printStackTrace();}pool.shutdown();}
}

在这段代码里,Executors.newFixedThreadPool(2)创建了一个包含 2 个线程的线程池,就像一个有 2 个固定工人的工厂。​

pool.execute(() -> System.out.println("工人1:打扫车间完成"));这行代码提交了一个Runnable任务,就相当于让工人 1 去打扫车间,打扫完就结束,不用汇报具体打扫情况。​

Future<Integer> future = pool.submit(() -> { System.out.println("工人2:清点货物中..."); return 100; }); 提交的是Callable任务,工人 2 去清点货物,完成后会把货物数量 100 件这个结果通过Future返回,就像工人 2 把清点结果汇报给老板。​

Thread threadTask = new Thread(() -> System.out.println("工人1:用自带步骤打扫仓库完成")); pool.execute(threadTask); 这里先创建了一个Thread任务,然后提交到线程池。虽然这个任务自带线程,但线程池还是按照自己的规则,只让工人 1 执行Thread里定义的打扫仓库步骤,忽略它自带的线程,就好比工人 1 带着自己的工具和步骤来打扫仓库,但工厂只用它的步骤,不用自带的工具。​

最后通过future.get()获取Callable任务的执行结果,打印出 “老板收到清点结果:100 件” 。整个执行结果如下(工人 1 和工人 2 是线程池里的固定线程):​

plaintext取消自动换行复制

工人1:打扫车间完成​

工人2:清点货物中...​

工人1:用自带步骤打扫仓库完成​

老板收到清点结果:100件​

从结果可以很明显地看出,线程池成功处理了这三种不同类型的任务单,它们之间不会相互影响,各自按照自己的特点执行任务 。​

为什么 Thread 不推荐使用?​

咱接着说,为什么不推荐把Thread当作任务单提交到线程池呢?这得从它们各自的设计初衷说起。Thread类的设计初衷是创建一个全新的线程,当你创建一个Thread实例并调用它的start方法时,系统会分配新的资源,开启一个全新的线程来执行任务。​

而线程池的核心价值在于复用已有的线程 。线程池预先创建好一定数量的线程放在 “池子” 里,有任务来的时候,直接从池子里取出空闲线程去执行,任务完成后,线程也不会被销毁,而是回到池子里等待下一个任务,这样就能避免频繁创建和销毁线程带来的资源开销。​

把Thread当作任务单提交到线程池,就好比工人带着自己的工具来工厂干活,但工厂有自己统一的工具,只需要工人按照任务单上的干活步骤操作就行,工人自带的工具完全派不上用场 。同样的道理,线程池有自己管理的线程,它不需要Thread自带的线程,只执行Thread里run方法的任务逻辑,这就显得多此一举,还浪费资源。​

所以在实际开发中,我们优先使用Runnable接口来定义不需要返回结果的任务,用Callable接口来定义需要返回结果的任务,这样能更好地契合线程池的工作模式,提高程序的性能和效率 。​

总结回顾,强化理解​

现在咱们再来回顾一下,线程池就像是工厂的老板,管理着一群固定的工人,也就是线程。而Runnable、Callable、Thread这三种,就像是不同类型的任务单。它们的功能和特点不一样,Runnable只干活不汇报结果,Callable干活后要汇报结果,Thread虽然自带线程,但提交到线程池时线程会被忽略 。​

不过不管是哪种任务单,线程池都能很好地处理。因为线程池只关心任务单里的干活逻辑,只要有任务单提交过来,它就安排空闲的工人去执行,不会因为任务单类型不同就有什么特殊对待。而且这三种任务单还能随意组合提交,就像工厂里工人一天可以干不同类型的活一样,在实际编程中,我们也能根据业务需求,灵活地把它们提交给线程池 。

2.线程使用

2.1异步处理:将耗时的操作异步化,提高系统响应速度

使用场景

在实际业务开发中,异步处理常用于处理那些不影响主流程但耗时较长的操作,例如在电商系统中,用户下单后需要发送确认邮件或短信通知。如果这些操作同步执行,会阻塞主线程,导致用户响应延迟增加。通过异步处理,可以立即返回下单成功响应,同时在后台线程中执行邮件/短信发送,从而提升系统的吞吐量和用户体验。另外,在Web服务中,处理文件上传或第三方API调用时,也常采用异步方式,避免主线程被阻塞,尤其在高并发场景下,能显著提高系统的响应速度。例如,一个在线报告生成系统,用户提交查询请求后,主线程返回“报告生成中”的响应,而后台线程异步生成PDF报告并上传到存储服务。

实现方式

在Java中,可以使用ExecutorService线程池来提交异步任务。以下是一个完整的示例:模拟电商下单后异步发送邮件。使用Executors.newFixedThreadPool创建线程池,提交Runnable任务。

import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;public class AsyncProcessingExample {public static void main(String[] args) {// 创建一个固定大小的线程池,假设最多5个线程ExecutorService executorService = Executors.newFixedThreadPool(5);// 模拟用户下单操作System.out.println("用户下单开始...");// 异步发送邮件executorService.submit(() -> {try {// 模拟耗时操作:发送邮件Thread.sleep(3000); // 假设发送邮件需要3秒System.out.println("邮件发送成功:订单确认邮件已发送");} catch (InterruptedException e) {Thread.currentThread().interrupt();System.err.println("邮件发送中断");}});// 主线程立即返回响应System.out.println("用户下单成功,响应返回");// 关闭线程池(实际生产中可根据需要管理生命周期)executorService.shutdown();}
}

在上述代码中,主线程提交任务后立即继续执行,返回响应,而邮件发送在后台线程中进行。如果需要获取异步结果,可以使用Future或CompletableFuture(详见2.8)。

2.2定时任务:使用线程池执行定时任务,比如定期数据同步、状态检查等

使用场景

定时任务在业务中非常常见,例如在金融系统中,每日凌晨同步银行对账数据;在监控系统中,每5分钟检查服务器状态并报警;在电商库存系统中,每小时更新热门商品的缓存。如果使用单线程定时,会导致任务阻塞;通过线程池,可以并行执行多个定时任务,提高效率。例如,一个日志分析系统,需要每10分钟从数据库拉取日志数据,进行聚合分析并存储结果。如果任务耗时长,使用线程池可以确保定时器不被阻塞,同时控制并发数避免资源耗尽。

实现方式

Java提供了ScheduledExecutorService接口来实现定时任务。以下示例使用Executors.newScheduledThreadPool创建调度线程池,模拟每5秒执行一次数据同步任务。

import java.util.concurrent.Executors;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.TimeUnit;public class ScheduledTaskExample {public static void main(String[] args) {// 创建一个调度线程池,核心线程数为1(可根据任务并行度调整)ScheduledExecutorService scheduledExecutor = Executors.newScheduledThreadPool(1);// 调度任务:初始延迟0秒,每5秒执行一次scheduledExecutor.scheduleAtFixedRate(() -> {try {// 模拟耗时任务:数据同步Thread.sleep(2000); // 假设同步需要2秒System.out.println("数据同步完成:" + System.currentTimeMillis());} catch (InterruptedException e) {Thread.currentThread().interrupt();System.err.println("数据同步中断");}}, 0, 5, TimeUnit.SECONDS);// 让主线程运行一段时间观察效果,然后关闭(实际生产中可不关闭)try {Thread.sleep(20000); // 运行20秒} catch (InterruptedException e) {Thread.currentThread().interrupt();}scheduledExecutor.shutdown();}
}

此代码中,scheduleAtFixedRate确保任务按固定速率执行,即使上一个任务未完成,也会启动新任务(如果需要等待上一个完成,可用scheduleWithFixedDelay)。在生产中,可结合Quartz或Spring Scheduled注解实现更复杂的调度。

2.3生产者-消费者模式:解耦生产数据和消费数据的过程,通过队列平衡生产者和消费者的速度差异

使用场景

生产者-消费者模式常用于解耦系统模块,例如在消息队列系统中,生产者线程生成日志事件,消费者线程处理并存储到数据库。如果生产速度快于消费,队列可以缓冲数据,避免生产者阻塞;在大数据处理中,生产者从网络读取数据,消费者进行分析和聚合。例如,一个实时监控系统,生产者线程从传感器采集数据(高频),消费者线程处理数据并更新Dashboard,通过阻塞队列平衡速度差异,防止内存溢出或数据丢失。

实现方式

使用BlockingQueue(如ArrayBlockingQueue)作为缓冲区,生产者和消费者线程分别put和take。以下示例模拟生产者生成数字,消费者消费并打印。

import java.util.concurrent.ArrayBlockingQueue;
import java.util.concurrent.BlockingQueue;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;public class ProducerConsumerExample {public static void main(String[] args) {// 创建容量为10的阻塞队列BlockingQueue<Integer> queue = new ArrayBlockingQueue<>(10);// 创建线程池ExecutorService executorService = Executors.newFixedThreadPool(2);// 生产者线程executorService.submit(() -> {try {for (int i = 1; i <= 20; i++) {queue.put(i); // 如果队列满,会阻塞System.out.println("生产者生产: " + i);Thread.sleep(500); // 模拟生产耗时}} catch (InterruptedException e) {Thread.currentThread().interrupt();}});// 消费者线程executorService.submit(() -> {try {for (int i = 1; i <= 20; i++) {int value = queue.take(); // 如果队列空,会阻塞System.out.println("消费者消费: " + value);Thread.sleep(1000); // 模拟消费耗时慢于生产}} catch (InterruptedException e) {Thread.currentThread().interrupt();}});executorService.shutdown();}
}

队列自动处理阻塞,确保生产者和消费者解耦。在实际中,可用Kafka或RabbitMQ替换简单队列以支持分布式。

2.4并行处理:将一个大任务拆分成多个小任务,并行处理以提高效率

详细解释:并行处理利用多核CPU,同时执行多个子任务。Java通过线程池提交子任务,收集结果。关键是任务可独立(无依赖),使用Future或Stream API管理。相比串行,它减少总时间,但增加协调开销(如同步结果)。注意数据竞争,使用线程安全结构。

实际适用场景

  • 大数据聚合:在报表系统中,从数据库查询百万条记录,分块并行计算平均值/总和,减少响应时间从分钟到秒。
  • 图像/视频处理:在媒体App中,将视频帧分成组,并行应用滤镜或编码,加速渲染。
  • 批量操作:在CRM系统中,并行发送批量邮件,避免单线程阻塞。
  • 科学计算:模拟蒙特卡洛方法,分块并行运行模拟,提高精度和速度。

更详细代码:扩展数组求和示例为并行处理大型矩阵求和,添加异常处理和动态拆分。

import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.*;public class ParallelProcessingDetailedExample {public static void main(String[] args) {// 模拟一个大型矩阵:100行 x 1000列int[][] matrix = new int[100][1000];for (int i = 0; i < matrix.length; i++) {for (int j = 0; j < matrix[i].length; j++) {matrix[i][j] = 1; // 填充1}}// 创建线程池,线程数基于CPU核心int numCores = Runtime.getRuntime().availableProcessors();ExecutorService executor = Executors.newFixedThreadPool(numCores);// 动态拆分成子任务:每个任务处理一行List<Callable<Long>> tasks = new ArrayList<>();for (int row = 0; row < matrix.length; row++) {final int currentRow = row;tasks.add(() -> sumRow(matrix[currentRow]));}// 并行执行所有任务long totalSum = 0;try {List<Future<Long>> futures = executor.invokeAll(tasks);for (Future<Long> future : futures) {totalSum += future.get(); // 收集结果}System.out.println("矩阵总和: " + totalSum);} catch (InterruptedException | ExecutionException e) {System.err.println("并行处理异常: " + e.getMessage());Thread.currentThread().interrupt();} finally {executor.shutdown();}}private static long sumRow(int[] row) {long sum = 0;for (int value : row) {sum += value;// 模拟耗时计算try {Thread.sleep(1); // 每元素1ms} catch (InterruptedException e) {Thread.currentThread().interrupt();throw new RuntimeException("行求和中断");}}System.out.println("行求和完成: " + sum + " (线程: " + Thread.currentThread().getName() + ")");return sum;}
}

代码解释

  • 矩阵初始化:创建大型数据模拟实际大数据。
  • 线程池创建:使用availableProcessors()动态设置线程数,优化多核利用。
  • 任务拆分:循环创建Callable任务,每行一个,确保独立。
  • invokeAll:并行提交所有任务,返回Future列表。
  • 结果收集:循环future.get()汇总,处理异常(InterruptedException中断,ExecutionException任务内异常)。
  • sumRow 方法:计算单行和,添加sleep模拟耗时,打印线程名显示并行。
  • finally:确保关闭池,释放资源。
  • 运行时,你会看到多个线程同时打印,总时间远小于串行(100行 x 1000 x 1ms = 100s vs. 并行 ~100s / 核心数)。

2.5工作窃取(Work Stealing):使用ForkJoinPool,将大任务拆分成小任务,空闲线程可以从其他线程的任务队列中窃取任务来执行

详细解释:工作窃取是Fork/Join框架的核心,适用于递归分治算法。大任务通过fork()异步分解成小任务,join()等待结果。每个线程有双端队列(Deque),push/pop自家任务;空闲时从其他队列尾部steal任务。相比普通线程池,它自动负载均衡,减少空闲。

实际适用场景

  • 递归搜索:在文件管理系统中,从根目录递归搜索匹配文件,子目录任务可窃取,提高效率。
  • 并行排序/归并:快速排序数组,递归分段,窃取确保均衡。
  • 图形渲染:游戏引擎中,渲染场景树,复杂分支窃取简单任务。
  • 大数据MapReduce:类似Hadoop,map阶段分块,reduce窃取未处理块。
  • AI路径查找:图搜索算法,分支探索窃取。

更详细代码:扩展斐波那契为递归求和大型数组,添加阈值避免过度分解。

import java.util.concurrent.ForkJoinPool;
import java.util.concurrent.RecursiveTask;public class WorkStealingDetailedExample {private static final int THRESHOLD = 10; // 小任务阈值public static void main(String[] args) {// 模拟大型数组int[] array = new int[100];for (int i = 0; i < array.length; i++) {array[i] = i + 1; // 1到100}ForkJoinPool pool = new ForkJoinPool(); // 默认核心数SumTask task = new SumTask(array, 0, array.length);long result = pool.invoke(task);System.out.println("数组总和: " + result);}static class SumTask extends RecursiveTask<Long> {private final int[] array;private final int start;private final int end;SumTask(int[] array, int start, int end) {this.array = array;this.start = start;this.end = end;}@Overrideprotected Long compute() {int length = end - start;if (length <= THRESHOLD) {// 小任务直接计算long sum = 0;for (int i = start; i < end; i++) {sum += array[i];try {Thread.sleep(10); // 模拟耗时} catch (InterruptedException e) {Thread.currentThread().interrupt();throw new RuntimeException("计算中断");}}System.out.println("小任务完成: " + sum + " (范围 " + start + "-" + end + ", 线程: " + Thread.currentThread().getName() + ")");return sum;}// 分解int mid = start + length / 2;SumTask left = new SumTask(array, start, mid);SumTask right = new SumTask(array, mid, end);left.fork(); // 异步左子任务right.fork(); // 异步右子任务// 合并(窃取发生在这里)return left.join() + right.join();}}
}

代码解释

  • THRESHOLD:控制分解深度,避免小任务过多开销。
  • SumTask:RecursiveTask<Long>返回长整型。
  • compute():如果范围小,直接循环求和(sleep模拟不均衡耗时);否则,mid点分解,fork()提交子任务。
  • join():等待并获取结果,期间空闲线程窃取其他任务。
  • pool.invoke:启动根任务。
  • 输出显示任务分解和线程名,证明窃取(e.g., 一个线程完成多个小任务)。
  • 对于不均衡任务(如某些范围耗时长),窃取确保效率。

2.6线程池与资源管理:使用线程池管理线程,避免频繁创建和销毁线程的开销,同时控制并发数

详细解释:线程池通过复用线程减少创建/销毁开销(每个线程~1MB栈)。ThreadPoolExecutor允许自定义核心/最大线程、队列、拒绝策略。资源管理包括监控池大小、队列深度,防止OOM。相比直接new Thread,它控制并发,适合高负载。

实际适用场景

  • Web服务器:Tomcat使用线程池处理HTTP请求,限制并发避免崩溃。
  • 任务调度:后台Job系统,池化执行定时任务,控制CPU/内存。
  • API调用:微服务中,并发调用外部API,Semaphore结合限流。
  • 数据库操作:连接池类似,线程池管理查询线程,避免连接耗尽。
  • 游戏服务器:处理玩家请求,动态调整池大小。

更详细代码:自定义池,添加拒绝策略、监控。

import java.util.concurrent.*;public class ThreadPoolManagementDetailedExample {public static void main(String[] args) {// 自定义线程池BlockingQueue<Runnable> queue = new ArrayBlockingQueue<>(20); // 有界队列ThreadPoolExecutor executor = new ThreadPoolExecutor(5, // 核心线程:常驻10, // 最大线程:峰值时扩展60L, TimeUnit.SECONDS, // 空闲存活queue,Executors.defaultThreadFactory(), // 线程工厂new ThreadPoolExecutor.AbortPolicy() // 拒绝策略:抛异常);// 监控钩子executor.setRejectedExecutionHandler((r, e) -> System.err.println("任务拒绝: 队列满"));// 提交15个任务(核心5 + 队列20,但最大10)for (int i = 1; i <= 15; i++) {final int taskId = i;executor.execute(() -> {try {System.out.println("任务 " + taskId + " 开始 (线程: " + Thread.currentThread().getName() + ")");Thread.sleep(2000);System.out.println("任务 " + taskId + " 完成");} catch (InterruptedException e) {Thread.currentThread().interrupt();System.err.println("任务 " + taskId + " 中断");}});}// 监控池状态new Thread(() -> {while (!executor.isTerminated()) {System.out.println("池状态: 活跃线程=" + executor.getActiveCount() +", 队列大小=" + executor.getQueue().size() +", 完成任务=" + executor.getCompletedTaskCount());try {Thread.sleep(1000);} catch (InterruptedException e) {Thread.currentThread().interrupt();}}}).start();executor.shutdown();try {if (!executor.awaitTermination(60, TimeUnit.SECONDS)) {executor.shutdownNow();}} catch (InterruptedException e) {executor.shutdownNow();Thread.currentThread().interrupt();}}
}

代码解释

  • ThreadPoolExecutor 参数:核心5(最小运行),最大10(扩展),队列20(缓冲),空闲60s回收。
  • RejectedExecutionHandler:自定义拒绝,打印警告。
  • execute:提交任务,sleep模拟工作。
  • 监控线程:周期打印池指标(活跃、队列、完成),帮助调优。
  • shutdown & awaitTermination:优雅关闭,等待完成,若超时强制。
  • 输出显示池扩展到10,队列使用,证明资源控制。

2.7并发控制:使用CountDownLatch、CyclicBarrier、Semaphore等工具协调多个线程的执行

在Java的多线程编程中,协调多个线程的执行是确保程序正确性和高效性的关键。这些工具都来自于java.util.concurrent包,基于AbstractQueuedSynchronizer (AQS) 实现,提供了高效的同步机制,而非传统的synchronized或wait/notify。它们不是锁(不独占资源),而是辅助工具,用于特定场景的线程协作。下面我逐一超级详细解释每个工具的原理、内部机制、使用注意事项、优缺点、实际适用场景,然后提供代码,最后模拟执行过程(因为工具不支持Java运行,我会基于代码逻辑步步手动模拟可能的执行序列,包括线程调度的不确定性)。

CountDownLatch(倒计时锁)
  • 原理:CountDownLatch是一个一次性计数器,初始化时设置一个计数值N(new CountDownLatch(N))。线程调用countDown()时,计数减1;当计数达到0时,所有调用await()的线程被唤醒继续执行。它类似于一个闸门:子线程完成任务后“开门”,主线程等待所有门开后通过。
    • 内部机制:基于AQS的共享模式。状态(state)表示剩余计数。countDown()尝试CAS减state,当state=0时释放所有等待线程。await()如果state>0则park线程。
  • 使用注意事项
    • 一次性使用:计数到0后不能重置(如果需要重置,用CyclicBarrier)。
    • countDown()应在finally块中调用,确保异常时也减计。
    • 支持超时await(long timeout, TimeUnit unit)。
    • 不支持增加计数。
  • 优缺点
    • 优点:简单高效,适合“一等N”的场景。
    • 缺点:不可重用;如果线程异常未countDown,可能死锁。
  • 实际适用场景
    • 系统启动:主线程等待多个模块(如数据库连接、缓存加载、配置解析)初始化完成后再启动服务。例如,在Spring Boot应用中,等待所有Bean初始化。
    • 批量任务汇总:在数据导入系统中,多个线程并行导入分片数据,主线程等待所有完成后再合并结果或通知用户。
    • 测试场景:JMeter负载测试中,等待所有虚拟用户就绪后再同时发压。
    • 游戏开发:多人游戏中,等待所有玩家加载地图完成后开始回合。
CyclicBarrier(循环屏障)
  • 原理:CyclicBarrier是一个可重用的同步点,初始化时设置参与方数N(new CyclicBarrier(N)),可选Runnable在突破屏障时执行。每个线程调用await()时计数+1,当达到N时,所有线程被唤醒继续,且屏障重置为N。它像一个集合点:所有线程到达后集体出发。
    • 内部机制:使用ReentrantLock和Condition。内部计数器从N递减到0时唤醒所有,并执行Runnable,然后重置。支持破损检测isBroken()。
  • 使用注意事项
    • 可重用:适合多阶段同步。
    • 支持超时await(long timeout, TimeUnit unit)。
    • 如果线程数不足N,可能死锁。
    • Runnable在最后一个线程await后执行。
  • 优缺点
    • 优点:可重用,支持阶段性同步;自动重置。
    • 缺点:所有线程必须到达,否则挂起;不支持动态调整N。
  • 实际适用场景
    • 多阶段计算:在并行算法如遗传算法中,每个世代多个线程计算子种群,到达屏障后交换数据进入下一世代。
    • 模拟并发测试:模拟高并发场景,如电商秒杀,多个线程在屏障后同时访问资源测试锁争用。
    • 分布式任务:在MapReduce简化版中,map阶段线程到达屏障后开始reduce。
    • 动画渲染:多线程渲染帧,每帧多个线程处理部分,到达屏障后合成下一帧。
Semaphore(信号量)
  • 原理:Semaphore控制并发访问资源的许可数,初始化许可N(new Semaphore(N))。线程调用acquire()获取许可(减1),如果许可=0则阻塞;release()释放许可(加1)。它像停车场:有限车位,满了就等。
    • 内部机制:AQS共享模式。state表示可用许可。支持公平/非公平模式(new Semaphore(N, true)为公平)。
    • 可acquire多个许可acquire(int permits)。
  • 使用注意事项
    • release()不一定由acquire的线程调用(可用于生产者释放)。
    • 支持非阻塞tryAcquire()和超时。
    • 许可可初始为0,用于后续释放。
    • 避免死锁:acquire后必须release,通常在finally。
  • 优缺点
    • 优点:灵活限流,支持多许可操作。
    • 缺点:不记录谁持有许可;滥用易死锁。
  • 实际适用场景
    • 资源限流:数据库连接池,限制最大10个连接,线程acquire连接,用完release。
    • 流量控制:API网关中,Semaphore限制每秒QPS,避免下游服务崩溃。
    • 停车模拟:多线程模拟车辆进出停车场,许可=车位数。
    • 读写控制:实现简易读写锁,多读(acquire(1))单写(acquire(all))。
组合使用

这些工具可组合:如CountDownLatch等待启动,CyclicBarrier多阶段同步,Semaphore限并发。适用于复杂工作流,如批量处理数据:Semaphore限并发行数,CyclicBarrier同步聚合阶段,CountDownLatch等待整体完成。

代码示例
import java.util.concurrent.*;public class ConcurrencyControlDetailedExample {public static void main(String[] args) throws InterruptedException {ExecutorService executor = Executors.newFixedThreadPool(10);// CountDownLatch: Wait for 5 initialization tasksCountDownLatch initLatch = new CountDownLatch(5);for (int i = 1; i <= 5; i++) {final int id = i;executor.submit(() -> {try {System.out.println("Initialization task " + id + " starts");Thread.sleep((long) (Math.random() * 2000));System.out.println("Initialization task " + id + " completes");} catch (InterruptedException e) {Thread.currentThread().interrupt();} finally {initLatch.countDown();}});}System.out.println("Waiting for all initializations to complete...");initLatch.await();System.out.println("All initializations complete, continue main process");// CyclicBarrier: 4 threads, 2 stages synchronizationCyclicBarrier barrier = new CyclicBarrier(4, () -> System.out.println("Stage complete, all threads continue to next stage"));for (int i = 1; i <= 4; i++) {final int id = i;executor.submit(() -> {try {// Stage 1System.out.println("Thread " + id + " stage 1 starts");Thread.sleep((long) (Math.random() * 1000));System.out.println("Thread " + id + " reaches stage 1 end");barrier.await();// Stage 2System.out.println("Thread " + id + " stage 2 starts");Thread.sleep((long) (Math.random() * 1000));System.out.println("Thread " + id + " reaches stage 2 end");barrier.await();} catch (Exception e) {System.err.println("Thread " + id + " exception: " + e.getMessage());}});}// Semaphore: Limit 3 concurrent accesses to resourceSemaphore semaphore = new Semaphore(3);for (int i = 1; i <= 6; i++) {final int id = i;executor.submit(() -> {try {semaphore.acquire();System.out.println("Thread " + id + " acquires resource (current permits: " + semaphore.availablePermits() + ")");Thread.sleep(1500);} catch (InterruptedException e) {Thread.currentThread().interrupt();} finally {semaphore.release();System.out.println("Thread " + id + " releases resource");}});}executor.shutdown();executor.awaitTermination(1, TimeUnit.MINUTES);}
}

2.8 CompletableFuture组合异步编程:通过CompletableFuture实现复杂的异步编程,如多个异步任务的组合、回调等

CompletableFuture是Java 8引入的异步编程框架,扩展了Future接口,支持函数式编程风格的链式调用、组合、异常处理。它基于CompletionStage接口,允许构建复杂的异步管道,而非传统的Future的阻塞get()。它适合非阻塞、响应式编程,内部使用ForkJoinPool.commonPool()或自定义Executor。

核心原理

  • 创建:CompletableFuture.supplyAsync(Supplier)异步执行有返回任务;runAsync(Runnable)无返回。
  • 链式转换:thenApply(Function)同步转换结果;thenCompose(Function<CompletableFuture>)异步嵌套。
  • 组合:thenCombine(other, BiFunction)合并两个Future结果;allOf(varargs)等待所有完成;anyOf任一完成。
  • 回调:thenAccept(Consumer)消费结果;thenRun(Runnable)无参数运行。
  • 异常处理:exceptionally(Function<Throwable, T>)捕获异常返回默认;handle(BiFunction<T, Throwable, U>)处理正常/异常。
  • 内部机制:每个CompletableFuture有result和stack(待完成动作)。完成时触发栈中动作,支持递归触发。使用Treiber栈(CAS无锁)。
  • 使用注意事项
    • 默认线程池是ForkJoinPool.commonPool(), daemon线程,适合CPU密集;自定义Executor for IO。
    • 避免阻塞操作如get()在链中,用join()在末尾。
    • 支持超时orTimeout、completeOnTimeout (Java 9+)。
    • 异常传播:未处理异常会抛到get()/join()。
  • 优缺点
    • 优点:函数式、易组合、非阻塞;支持并行。
    • 缺点:学习曲线陡;复杂链易调试难;不适合非常长链(栈溢出风险)。
  • 实际适用场景
    • 微服务调用:异步调用用户服务、订单服务、支付服务,组合响应JSON。
    • 数据聚合:从多个源异步拉数据(如天气API),allOf等待,thenApply聚合。
    • 错误恢复:第三方API失败,用exceptionally fallback到缓存。
    • 批量异步:处理列表:CompletableFuture.allOf(list.stream().map(item -> supplyAsync(process(item))).toArray())。
    • Web响应:控制器异步处理请求,返回CompletableFuture<ResponseEntity>。

代码示例

import java.util.concurrent.*;
import java.util.Arrays;public class CompletableFutureDetailedExample {public static void main(String[] args) {// Custom thread poolExecutor executor = Executors.newFixedThreadPool(3);// Async task 1: Get userCompletableFuture<String> userFuture = CompletableFuture.supplyAsync(() -> {try {Thread.sleep(1000);return "User: Alice";} catch (InterruptedException e) {Thread.currentThread().interrupt();throw new CompletionException(e);}}, executor);// Async task 2: Get order (simulate failure)CompletableFuture<String> orderFuture = CompletableFuture.supplyAsync(() -> {try {Thread.sleep(2000);if (true) throw new RuntimeException("Order API failed");return "Order: #123";} catch (InterruptedException e) {Thread.currentThread().interrupt();throw new CompletionException(e);}}, executor).exceptionally(ex -> "Order: Default value (exception: " + ex.getMessage() + ")");// Async task 3: Get paymentCompletableFuture<String> paymentFuture = CompletableFuture.supplyAsync(() -> {try {Thread.sleep(1500);return "Payment: Completed";} catch (InterruptedException e) {Thread.currentThread().interrupt();throw new CompletionException(e);}}, executor);// Combine two: user + orderCompletableFuture<String> combined = userFuture.thenCombine(orderFuture, (user, order) -> user + ", " + order);// Aggregate allCompletableFuture<Void> allFutures = CompletableFuture.allOf(combined, paymentFuture);// Callback processingallFutures.thenAccept(v -> {try {String result = combined.get() + ", " + paymentFuture.get();System.out.println("Final result: " + result);} catch (Exception e) {System.err.println("Aggregation exception: " + e.getMessage());}}).exceptionally(ex -> {System.err.println("Overall exception: " + ex.getMessage());return null;});// Wait for completionallFutures.join();((ExecutorService) executor).shutdown();}
}

补充:

  • 3 个异步任务:分别获取用户、订单、支付信息(用supplyAsync创建,带返回值)
  • 1 个线程池:自定义的固定 3 线程池(避免用默认的守护线程池,方便控制关闭)
  • 组合操作:把用户和订单的结果合并,再和支付结果汇总
  • 异常处理:订单任务失败时用exceptionally返回默认值

step 1:初始化线程池 

Executor executor = Executors.newFixedThreadPool(3);

创建一个固定 3 个线程的线程池,后面 3 个异步任务会用这个线程池执行(避免抢占默认线程池资源)。

step 2:启动 3 个异步任务(并行执行)

3 个supplyAsync同时提交到线程池,线程池里的 3 个线程会分别执行它们(因为任务数 = 线程数,所以不会排队)。

任务 1:获取用户信息(耗时 1 秒)
CompletableFuture<String> userFuture = CompletableFuture.supplyAsync(() -> {Thread.sleep(1000); // 模拟耗时操作(比如调用用户API)return "User: Alice";
}, executor);
  • 线程池里的线程 A 执行这个任务,睡 1 秒后返回结果"User: Alice"
  • userFuture会记录这个结果,供后续使用。
任务 2:获取订单信息(耗时 2 秒,故意失败)
CompletableFuture<String> orderFuture = CompletableFuture.supplyAsync(() -> {Thread.sleep(2000); // 模拟耗时更长的操作throw new RuntimeException("Order API failed"); // 故意抛异常
}, executor).exceptionally(ex -> "Order: Default value (exception: " + ex.getMessage() + ")");
  • 线程池里的线程 B 执行这个任务,睡 2 秒后抛异常。
  • 关键:后面接了exceptionally,相当于 “如果前面失败了,就用这个函数返回默认值”。这里捕获异常后,返回"Order: Default value..."
  • 所以orderFuture最终的结果是这个默认值。
任务 3:获取支付信息(耗时 1.5 秒)
CompletableFuture<String> paymentFuture = CompletableFuture.supplyAsync(() -> {Thread.sleep(1500); // 模拟耗时操作return "Payment: Completed";
}, executor);
  • 线程池里的线程 C 执行这个任务,睡 1.5 秒后返回结果"Payment: Completed"
  • paymentFuture记录这个结果。

为什么这 3 个任务是并行的?

因为supplyAsync会立即把任务提交到线程池,线程池的 3 个线程同时工作,所以 3 个任务的执行时间是 “重叠” 的,而不是一个接一个等。比如:

  • 任务 1 用 1 秒,任务 3 用 1.5 秒,任务 2 用 2 秒。
  • 从启动开始算,1 秒后任务 1 先完成,1.5 秒后任务 3 完成,2 秒后任务 2(带着异常处理后的结果)完成。

step 3:组合用户和订单的结果(等两者完成后执行)

CompletableFuture<String> combined = userFuture.thenCombine(orderFuture, (user, order) -> user + ", " + order);
  • thenCombine的作用:userFutureorderFuture都完成后,把它们的结果传给后面的函数(这里是拼接字符串)。
  • 执行时机:任务 1(1 秒)和任务 2(2 秒)都完成后才会执行。因为任务 2 要 2 秒,所以这个组合操作会在第 2 秒时开始。
  • 结果:"User: Alice, Order: Default value..."(因为订单任务用了异常处理的默认值)。

step 4:等待所有任务完成(组合结果 + 支付结果)

CompletableFuture<Void> allFutures = CompletableFuture.allOf(combined, paymentFuture);
  • allOf的作用:等待传入的所有CompletableFuture都完成(这里是combinedpaymentFuture)。
  • 谁最后完成?combined在 2 秒完成,paymentFuture在 1.5 秒完成,所以allFutures会在 2 秒时完成(等最慢的那个)。
  • 注意:allOf返回的是CompletableFuture<Void>,不包含具体结果,只是标记 “所有任务都 done 了”。

step 5:所有任务完成后,汇总最终结果

allFutures.thenAccept(v -> {String result = combined.get() + ", " + paymentFuture.get();System.out.println("Final result: " + result);
}).exceptionally(ex -> {System.err.println("Overall exception: " + ex.getMessage());return null;
});
  • thenAccept的作用:allFutures完成后(也就是 2 秒时),执行这个回调函数(消费结果,无返回值)。
  • 这里为什么能用get()?因为allFutures已经确保combinedpaymentFuture都完成了,所以get()不会阻塞,直接拿结果。
  • 拼接结果:combined的结果(用户 + 订单)加上paymentFuture的结果(支付信息),最终输出:

    plaintext

    Final result: User: Alice, Order: Default value (exception: Order API failed), Payment: Completed
    
  • 后面的exceptionally是防止这个回调过程中出异常(这里正常执行,用不到)。

step 6:等待整个流程结束,关闭线程池

allFutures.join(); // 主线程等待allFutures完成(其实到这里已经快完成了,保险起见)
((ExecutorService) executor).shutdown(); // 关闭线程池,避免程序挂起
  • join()get()类似,但不需要捕获 checked 异常,适合在最后阻塞主线程,等所有异步操作完成。
  • 线程池必须手动关闭,否则程序不会退出。

CompletableFuture方法补充

在 CompletableFuture 中,除了 thenCombine(合并两个异步结果)和 allOf(等待所有任务完成),还有很多用于组合、串联异步任务的方法,它们都基于 CompletionStage 接口设计,用于处理不同的异步协作场景。以下是常用的类似方法,按功能分类说明:

一、合并两个异步任务的结果(类似 thenCombine)

这类方法用于等待两个 CompletableFuture 都完成后,对它们的结果进行处理,区别在于返回值和处理方式:

  1. thenCombine(CompletionStage, BiFunction)

    • 功能:等待两个任务完成,用 BiFunction 处理它们的结果,返回一个新的带结果的 CompletableFuture
    • 示例:
      // 任务A:返回数字1
      CompletableFuture<Integer> a = CompletableFuture.supplyAsync(() -> 1);
      // 任务B:返回数字2
      CompletableFuture<Integer> b = CompletableFuture.supplyAsync(() -> 2);
      // 合并结果:1 + 2 = 3
      CompletableFuture<Integer> sum = a.thenCombine(b, (x, y) -> x + y);
      
  2. thenAcceptBoth(CompletionStage, BiConsumer)

    • 功能:等待两个任务完成,用 BiConsumer 消费它们的结果(无返回值)。
    • 示例:
      a.thenAcceptBoth(b, (x, y) -> System.out.println("结果:" + x + ", " + y)); 
      // 输出:结果:1, 2
      
  3. runAfterBoth(CompletionStage, Runnable)

    • 功能:等待两个任务完成后,执行一个无参数的 Runnable(不关心前两个任务的结果)。
    • 示例:
      a.runAfterBoth(b, () -> System.out.println("两个任务都完成了"));
      

二、等待多个任务(类似 allOf)

这类方法用于处理多个异步任务的 “等待” 逻辑,区别在于 “等待全部完成” 还是 “等待任一完成”:

  1. allOf(CompletableFuture<?>...)

    • 功能:等待所有传入的任务全部完成(无论成功失败),返回一个 CompletableFuture<Void>(无结果,仅标记完成状态)。
    • 注意:如果需要获取每个任务的结果,需单独调用 get() 或 join()
    • 示例:
      CompletableFuture<Void> allDone = CompletableFuture.allOf(a, b, c);
      allDone.thenRun(() -> {// 所有任务完成后执行System.out.println("a的结果:" + a.join());
      });
      
  2. anyOf(CompletableFuture<?>...)

    • 功能:等待传入的任务中任意一个完成(第一个完成的),返回一个 CompletableFuture<Object>(结果为第一个完成的任务的结果)。
    • 示例:
      // 任务1:1秒后返回"快"
      CompletableFuture<String> fast = CompletableFuture.supplyAsync(() -> {try { Thread.sleep(1000); } catch (InterruptedException e) {}return "快";
      });
      // 任务2:2秒后返回"慢"
      CompletableFuture<String> slow = CompletableFuture.supplyAsync(() -> {try { Thread.sleep(2000); } catch (InterruptedException e) {}return "慢";
      });
      // 等待第一个完成的任务
      CompletableFuture<Object> firstDone = CompletableFuture.anyOf(fast, slow);
      firstDone.thenAccept(result -> System.out.println("第一个完成的结果:" + result)); 
      // 输出:第一个完成的结果:快(1秒后执行)
      

三、串联异步任务(任务 B 依赖任务 A 的结果)

这类方法用于让一个任务在另一个任务完成后执行,且可以使用前一个任务的结果,避免嵌套回调(“链式调用” 的核心):

  1. thenApply(Function)

    • 功能:当前任务完成后,用 Function 处理其结果,返回一个新的带结果的 CompletableFuture(同步执行 Function)。
    • 示例:
      CompletableFuture<Integer> a = CompletableFuture.supplyAsync(() -> 1);
      // a完成后,结果+1
      CompletableFuture<Integer> b = a.thenApply(x -> x + 1); // 结果为2
      
  2. thenCompose(Function)

    • 功能:当前任务完成后,用 Function 生成一个新的 CompletableFuture(异步嵌套场景,避免返回 CompletableFuture<CompletableFuture<T>>)。
    • 示例:
      // 任务1:返回用户ID
      CompletableFuture<String> getUserId = CompletableFuture.supplyAsync(() -> "123");
      // 任务2:根据用户ID获取用户详情(需要任务1的结果)
      CompletableFuture<User> getUser = getUserId.thenCompose(userId -> CompletableFuture.supplyAsync(() -> new User(userId)) // 生成新的异步任务
      );
      
    • 区别于 thenApplythenApply 会返回 CompletableFuture<CompletableFuture<User>>(嵌套),而 thenCompose 会 “展开” 为 CompletableFuture<User>
  3. thenAccept(Consumer)

    • 功能:当前任务完成后,用 Consumer 消费其结果(无返回值)。
    • 示例:
      a.thenAccept(x -> System.out.println("消费结果:" + x)); // 输出:消费结果:1
      
  4. thenRun(Runnable)

    • 功能:当前任务完成后,执行一个无参数的 Runnable(不关心前一个任务的结果)。
    • 示例:
      a.thenRun(() -> System.out.println("任务A完成了,我执行一下"));
      

四、异步执行的变体(带线程池参数)

上面的很多方法都有对应的 “异步版本”(方法名后加 Async),用于指定线程池执行后续任务,避免占用前一个任务的线程:

  • thenApplyAsync(Function)
  • thenComposeAsync(Function)
  • thenCombineAsync(CompletionStage, BiFunction)
  • 等等...

示例

CompletableFuture<Integer> a = CompletableFuture.supplyAsync(() -> 1);
// 使用自定义线程池异步处理a的结果
a.thenApplyAsync(x -> x + 1, executor); 
  • 非 Async 版本:默认在当前任务的线程(或调用线程)中执行后续操作。
  • Async 版本:在指定的线程池(或默认线程池)中异步执行后续操作,更适合高并发场景。

五、异常处理相关(确保链不中断)

虽然不直接用于 “组合任务”,但异常处理是复杂异步链的重要部分,常用方法:

  1. exceptionally(Function<Throwable, T>)

    • 功能:当前任务抛出异常时,用 Function 返回一个默认值(“兜底”),让链继续执行。
    • 示例:
      CompletableFuture<Integer> task = CompletableFuture.supplyAsync(() -> {throw new RuntimeException("出错了");
      }).exceptionally(ex -> {System.out.println("捕获异常:" + ex.getMessage());return 0; // 返回默认值
      });
      // 结果为0,而非异常
      
  2. handle(BiFunction<T, Throwable, U>)

    • 功能:无论当前任务成功还是失败,都执行处理(同时接收结果和异常)。
    • 示例:
      CompletableFuture<Integer> task = CompletableFuture.supplyAsync(() -> 1).handle((result, ex) -> {if (ex != null) {System.out.println("异常:" + ex.getMessage());return 0;} else {return result + 1; // 正常处理}});
      // 结果为2
      

总结:核心方法分类表

场景常用方法作用描述
合并两个任务结果thenCombinethenAcceptBoth等待两个任务完成,处理 / 消费结果
等待多个任务allOf(全部完成)、anyOf(任一完成)批量处理异步任务的等待逻辑
串联任务(依赖结果)thenApplythenCompose用前一个任务的结果启动下一个任务
不依赖结果的串联thenRunrunAfterBoth任务完成后执行固定逻辑(不关心结果)
异步执行变体方法名 +Async(如thenApplyAsync指定线程池执行后续任务,避免阻塞
异常处理exceptionallyhandle捕获异常并兜底,确保异步链不中断

这些方法可以灵活组合,构建复杂的异步流程(比如 “先并行执行 A、B、C,再用 A 和 B 的结果执行 D,最后用 C 和 D 的结果执行 E” 等);

http://www.dtcms.com/a/570667.html

相关文章:

  • 广州 餐饮 网站建设微网站策划方案
  • 公司网站首页怎么制作网站建设邯郸
  • 网站开发工具的功能有哪些建站平台工具
  • Ie8网站后台编辑文章wordpress外贸模版
  • 读书笔记|理财,锻炼,阅读,思考
  • 【产品调研】运动生物力学软件工具对比
  • 马卡龙网站建设方案阿里巴巴国际站费用
  • 网站开发融资计划徐州列表网
  • 如何验证数据一致性?
  • 湘潭网站公司百度站长网站文件验证
  • KingSCADA项目遇到的几个问题
  • 电子学会青少年软件编程(C/C++)4级等级考试真题试卷(2025年9月)
  • 教做宝宝辅食的网站网站建设的方案计划
  • 【仓颉纪元】仓颉标准库源码深度拆解:探秘高性能实现之道
  • 四川网站开发制作青岛网站建设方案托管
  • DeepAgent:工具检索、工具调用与思维折叠的统一智能体框架深度解析
  • 古田网站建设域名服务网站
  • 兼职网站建设收费上海市建设部注册中心网站
  • 网上下的网站模版后门成都麦卡网络做网站开发怎么样
  • 计算机操作系统:文件目录
  • 安桌系统adb操作
  • 网站开发从哪开始学最近的头条新闻
  • 做网页怎么建站点有什么免费开发网站建设软件有哪些
  • 服装网站建设平台分析做网站时需要FTP工具吗
  • 网上做兼职的网站大气黑色机械企业网站源码
  • 环保网站开发superstore wordpress下载
  • 流匹配动作生成
  • 构建AI智能体:八十四、大模型涌现能力的解构分析:从量变到质变的神秘跃迁
  • 综合型企业网站有哪些在线做网站索引
  • 中天建设招标网站中国楼市最新消息已出