news 2026/4/16 9:22:47

LatchUtils:简化Java异步任务同步的利器

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
LatchUtils:简化Java异步任务同步的利器

在Java应用开发中,为了提升系统性能和响应速度,我们经常需要将一些耗时操作(如调用外部API、查询数据库、复杂计算等)进行异步并行处理。当主流程需要等待所有这些并行任务执行完毕后再继续时,我们通常会用到ExecutorServiceCountDownLatch等并发工具。

然而,直接使用这些原生工具,往往意味着需要编写一些重复的、模式化的“胶水代码”,这不仅增加了代码量,也让核心业务逻辑显得不够清晰。

为了解决这个问题,我封装了一个名为LatchUtils的轻量级工具类。它能够以一种极其简洁的方式来组织和管理这一类异步任务。

详细代码

其代码如下,后面会有使用说明和示例以及和传统实现代码的对比

import java.util.LinkedList; import java.util.List; import java.util.concurrent.CountDownLatch; import java.util.concurrent.Executor; import java.util.concurrent.TimeUnit; public class LatchUtils { private static final ThreadLocal<List<TaskInfo>> THREADLOCAL = ThreadLocal.withInitial(LinkedList::new); public static void submitTask(Executor executor, Runnable runnable) { THREADLOCAL.get().add(new TaskInfo(executor, runnable)); } private static List<TaskInfo> popTask() { List<TaskInfo> taskInfos = THREADLOCAL.get(); THREADLOCAL.remove(); return taskInfos; } public static boolean waitFor(long timeout, TimeUnit timeUnit) { List<TaskInfo> taskInfos = popTask(); if (taskInfos.isEmpty()) { return true; } CountDownLatch latch = new CountDownLatch(taskInfos.size()); for (TaskInfo taskInfo : taskInfos) { Executor executor = taskInfo.executor; Runnable runnable = taskInfo.runnable; executor.execute(() -> { try { runnable.run(); } finally { latch.countDown(); } }); } boolean await = false; try { await = latch.await(timeout, timeUnit); } catch (Exception ignored) { } return await; } private static final class TaskInfo { private final Executor executor; private final Runnable runnable; public TaskInfo(Executor executor, Runnable runnable) { this.executor = executor; this.runnable = runnable; } } }

核心思想

LatchUtils的设计哲学是:多次提交,一次等待。

  • 任务注册:在主流程代码中,可以先通过LatchUtils.submitTask()提交Runnable任务和其对应的Executor(该线程池用来执行这个Runnable)。

  • 执行并等待:当并行任务都提交完毕后,你只需调用一次LatchUtils.waitFor()。关注工众号:码猿技术专栏,回复关键词:1111 获取阿里内部Java性能调优手册!该方法会立即触发所有已注册任务的执行,并阻塞等待所有任务执行完成或超时。

API 概览

这个工具类对外暴露的接口极其简单,只有两个核心静态方法:

submitTask()

public static void submitTask(Executor executor, Runnable runnable)

功能:提交一个异步任务。

参数:

  • executor:java.util.concurrent.Executor- 指定执行此任务的线程池。

  • runnable:java.lang.Runnable- 需要异步执行的具体业务逻辑。

waitFor()

public static boolean waitFor(long timeout, TimeUnit timeUnit)

功能:触发所有已提交任务的执行,并同步等待它们全部完成。

参数:

  • timeout:long- 最长等待时间。

  • timeUnit:java.util.concurrent.TimeUnit- 等待时间单位。

返回值:

  • true:如果所有任务在指定时间内成功完成。

  • false:如果等待超时。

注意:该方法在执行后会自动清理当前线程提交的任务列表,因此可以重复使用。

实战示例

让我们来看一个典型的应用场景:一个聚合服务需要同时调用用户服务、订单服务和商品服务,拿到所有结果后再进行下一步处理。

import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; import java.util.concurrent.TimeUnit; public class Main { public static void main(String[] args) { // 1. 准备一个线程池 ExecutorService executorService = Executors.newFixedThreadPool(3); System.out.println("主流程开始,准备分发异步任务..."); // 2. 提交多个异步任务 // 任务一:获取用户信息 LatchUtils.submitTask(executorService, () -> { try { System.out.println("开始获取用户信息..."); Thread.sleep(1000); // 模拟耗时 System.out.println("获取用户信息成功!"); } catch (InterruptedException e) { Thread.currentThread().interrupt(); } }); // 任务二:获取订单信息 LatchUtils.submitTask(executorService, () -> { try { System.out.println("开始获取订单信息..."); Thread.sleep(1500); // 模拟耗时 System.out.println("获取订单信息成功!"); } catch (InterruptedException e) { Thread.currentThread().interrupt(); } }); // 任务三:获取商品信息 LatchUtils.submitTask(executorService, () -> { try { System.out.println("开始获取商品信息..."); Thread.sleep(500); // 模拟耗时 System.out.println("获取商品信息成功!"); } catch (InterruptedException e) { Thread.currentThread().interrupt(); } }); System.out.println("所有异步任务已提交,主线程开始等待..."); // 3. 等待所有任务完成,最长等待5秒 boolean allTasksCompleted = LatchUtils.waitFor(5, TimeUnit.SECONDS); // 4. 根据等待结果继续主流程 if (allTasksCompleted) { System.out.println("所有异步任务执行成功,主流程继续..."); } else { System.err.println("有任务执行超时,主流程中断!"); } // 5. 关闭线程池 executorService.shutdown(); } }

输出结果:

主流程开始,准备分发异步任务... 所有异步任务已提交,主线程开始等待... 开始获取商品信息... 开始获取用户信息... 开始获取订单信息... 获取商品信息成功! 获取用户信息成功! 获取订单信息成功! 所有异步任务执行成功,主流程继续...

从这个例子中可以看到,业务代码变得非常清晰。我们只需要关注“提交任务”和“等待结果”这两个动作,而无需关心CountDownLatch的初始化、countDown()的调用以及异常处理等细节。

对比:如果不使用 LatchUtils

为了更好地理解LatchUtils带来的价值,让我们看看要实现与上面完全相同的功能,用传统的Java并发API需要如何编写代码。
通常有两种主流方式:使用CountDownLatch或使用CompletableFuture

方式一:直接使用 CountDownLatch

这是最经典的方式,开发者需要手动管理CountDownLatch的生命周期。

import java.util.concurrent.CountDownLatch; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; import java.util.concurrent.TimeUnit; public class ManualCountDownLatchExample { public static void main(String[] args) { // 1. 准备一个线程池 ExecutorService executorService = Executors.newFixedThreadPool(3); // 2. 手动初始化 CountDownLatch,数量为任务数 CountDownLatch latch = new CountDownLatch(3); System.out.println("主流程开始,准备分发异步任务..."); // 3. 提交任务,并在每个任务的 finally 块中手动调用 latch.countDown() // 任务一:获取用户信息 executorService.execute(() -> { try { System.out.println("开始获取用户信息..."); Thread.sleep(1000); System.out.println("获取用户信息成功!"); } catch (InterruptedException e) { Thread.currentThread().interrupt(); } finally { latch.countDown(); // 手动减一 } }); // 任务二:获取订单信息 executorService.execute(() -> { try { System.out.println("开始获取订单信息..."); Thread.sleep(1500); System.out.println("获取订单信息成功!"); } catch (InterruptedException e) { Thread.currentThread().interrupt(); } finally { latch.countDown(); // 手动减一 } }); // 任务三:获取商品信息 executorService.execute(() -> { try { System.out.println("开始获取商品信息..."); Thread.sleep(500); System.out.println("获取商品信息成功!"); } catch (InterruptedException e) { Thread.currentThread().interrupt(); } finally { latch.countDown(); // 手动减一 } }); System.out.println("所有异步任务已提交,主线程开始等待..."); // 4. 手动调用 latch.await() 进行等待 boolean allTasksCompleted = false; try { allTasksCompleted = latch.await(5, TimeUnit.SECONDS); } catch (InterruptedException e) { // 需要处理中断异常 Thread.currentThread().interrupt(); System.err.println("主线程在等待时被中断!"); } // 5. 根据等待结果继续主流程 if (allTasksCompleted) { System.out.println("所有异步任务执行成功,主流程继续..."); } else { System.err.println("有任务执行超时,主流程中断!"); } // 6. 关闭线程池 executorService.shutdown(); } }

方式二:使用 CompletableFuture

使用CompletableFuture实现,其代码如下

import java.util.concurrent.CompletableFuture; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; import java.util.concurrent.TimeUnit; public class CompletableFutureExample { public static void main(String[] args) { // 1. 准备一个线程池 ExecutorService executorService = Executors.newFixedThreadPool(3); System.out.println("主流程开始,准备分发异步任务..."); // 2. 创建 CompletableFuture 任务 CompletableFuture<Void> userFuture = CompletableFuture.runAsync(() -> { try { System.out.println("开始获取用户信息..."); Thread.sleep(1000); System.out.println("获取用户信息成功!"); } catch (InterruptedException e) { Thread.currentThread().interrupt(); } }, executorService); CompletableFuture<Void> orderFuture = CompletableFuture.runAsync(() -> { try { System.out.println("开始获取订单信息..."); Thread.sleep(1500); System.out.println("获取订单信息成功!"); } catch (InterruptedException e) { Thread.currentThread().interrupt(); } }, executorService); CompletableFuture<Void> productFuture = CompletableFuture.runAsync(() -> { try { System.out.println("开始获取商品信息..."); Thread.sleep(500); System.out.println("获取商品信息成功!"); } catch (InterruptedException e) { Thread.currentThread().interrupt(); } }, executorService); System.out.println("所有异步任务已提交,主线程开始等待..."); // 3. 使用 CompletableFuture.allOf 将所有任务组合起来 CompletableFuture<Void> allFutures = CompletableFuture.allOf(userFuture, orderFuture, productFuture); // 4. 等待组合后的 Future 完成 try { allFutures.get(5, TimeUnit.SECONDS); System.out.println("所有异步任务执行成功,主流程继续..."); } catch (Exception e) { // 需要处理多种异常,如 InterruptedException, ExecutionException, TimeoutException System.err.println("任务执行超时或出错,主流程中断! " + e.getMessage()); } // 5. 关闭线程池 executorService.shutdown(); } }

对比分析

特性

LatchUtils

手动CountDownLatch

CompletableFuture.allOf

代码简洁性极高

。业务逻辑和并发控制分离,核心代码清晰。

中等

。需要在每个任务中嵌入latch.countDown(),分散了关注点。

较高

。链式调用风格,但需要创建多个Future对象。

状态管理自动

。工具类内部自动管理CountDownLatch。

手动

。需要自己创建、维护和传递CountDownLatch实例。

自动

。由CompletableFuture框架管理任务状态。

错误处理简化

。waitFor内部处理InterruptedException,仅返回布尔值。

复杂

。需要显式地在finally中countDown(),并为主线程的await()处理InterruptedException。

复杂

。get()方法会抛出多种受检异常,需要统一处理。

关注点分离优秀

。开发者只需关注“提交”和“等待”两个动作。

一般

。并发控制逻辑(countDown())侵入到了业务Runnable中。

良好

。任务的定义和组合是分开的,但仍需处理组合后的Future。

易用性非常简单

。几乎没有学习成本。

需要理解CountDownLatch

。容易忘记countDown()或错误处理。

需要理解CompletableFuture

。API较为丰富,有一定学习曲线。

结论很明显:

对于“分发一组并行任务,然后等待它们全部完成”这一特定但常见的模式,LatchUtils 通过适度的封装,极大地简化了开发者的工作。它隐藏了并发控制的复杂性,让业务代码回归其本质,从而提高了代码的可读性和可维护性。

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/4/15 21:31:33

AI辅助尽调智能体:重构尽调效率与精准度的技术内核

在金融、投行等领域&#xff0c;尽职调查是风险防控的核心环节&#xff0c;曾长期依赖“人工翻阅、手动核算、经验判断”的传统模式&#xff0c;效率低、漏判风险高。而AI辅助尽调智能体的出现&#xff0c;并非简单替代人工&#xff0c;而是通过技术赋能搭建“人机协同”新范式…

作者头像 李华
网站建设 2026/4/7 9:21:45

专科生必看!当红之选的AI论文软件 —— 千笔AI

专科生必看&#xff01;当红之选的AI论文软件 —— 千笔AI你是否曾为论文选题而焦虑不已&#xff1f;是否在深夜里面对空白文档无从下笔&#xff1f;是否反复修改却仍对表达不满意&#xff1f;论文写作的每一个环节都像是一场与时间的赛跑&#xff0c;而你却常常被卡在半路。别…

作者头像 李华
网站建设 2026/4/12 18:57:35

乐鑫信息 ESP32-WROVER-IE(16MB) SMD WiFi模块

ESP32-WROVER-E 和 ESP32-WROVER-IE 是两款通用型 Wi-Fi Bluetooth Bluetooth LE MCU 模组&#xff0c;功能强 大&#xff0c;用途广泛&#xff0c;可以用于低功耗传感器网络和要求极高的任务&#xff0c;例如语音编码、音频流和MP3解码等

作者头像 李华
网站建设 2026/3/24 4:20:43

基于51单片机的抢答器设计

基于51单片机的抢答器设计 一、设计背景与意义 抢答器是知识竞赛、课堂互动、技能比拼等场景的核心设备&#xff0c;传统简易抢答器存在抢答判定不精准、功能单一、无防作弊机制等问题&#xff0c;而商用智能抢答器成本高、操作复杂&#xff0c;难以满足中小学校、小型企业的…

作者头像 李华