在高并发系统中,简单往往意味着稳定。LatchUtils 并不是为了替代 JDK 的并发框架,而是为了在特定场景下,提供一种更符合直觉的方式来管理异步任务。
在现代 Java 应用开发中,并发几乎是提升性能的“标配”。无论是并行调用多个微服务接口、批量查询数据库,还是执行密集计算,我们都离不开异步与并行。
然而,当主线程需要等待多个异步任务完成后再继续执行时,开发者往往需要手动编写大量控制逻辑。例如使用 ExecutorService、CountDownLatch、CompletableFuture 等工具时,总会出现冗余的样板代码(如 latch.countDown()、异常处理、await 等)。
这些重复性逻辑不仅分散了业务重点,还让代码显得笨重。
于是,我们引入了一个轻量级的并发封装工具 —— LatchUtils。 它秉持“多次提交,一次等待”的核心设计理念,让异步任务的管理变得极其简洁。
设计思想:多次提交,一次等待
LatchUtils 的核心思想是将任务注册与任务等待分离:
- 任务注册阶段:通过
submitTask()方法注册多个任务及其对应线程池; - 等待阶段:在所有任务提交完成后,调用一次
waitFor()即可触发执行并同步等待。
简单来说,你只需要两步:
- 提交所有异步任务
- 等待全部执行完毕
而不需要再关心 CountDownLatch 的创建、计数或中断异常。
项目路径结构
复制
/src
└── main
└── java
└── com
└── icoderoad
└── utils
└── LatchUtils.java
- 1.
- 2.
- 3.
- 4.
- 5.
- 6.
- 7.
核心代码实现
以下是经过优化的 LatchUtils 实现。 相比传统写法,它自动管理任务生命周期,使用 ThreadLocal 确保任务隔离,让多线程调用更安全。
复制
package com.icoderoad.utils;
import java.util.LinkedList;
import java.util.List;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.Executor;
import java.util.concurrent.TimeUnit;
/**
* LatchUtils - 轻量级异步任务协调工具
* 核心设计:多次提交,一次等待。
*/
public class LatchUtils {
// 使用 ThreadLocal 存储当前线程提交的任务列表,保证线程隔离
private static final ThreadLocal<List<TaskInfo>> TASK_POOL = ThreadLocal.withInitial(LinkedList::new);
/**
* 提交异步任务
* @param executor 指定线程池执行任务
* @param runnable 任务逻辑
*/
public static void submitTask(Executor executor, Runnable runnable) {
TASK_POOL.get().add(new TaskInfo(executor, runnable));
}
// 获取并清空任务列表
private static List<TaskInfo> popTask() {
List<TaskInfo> taskInfos = TASK_POOL.get();
TASK_POOL.remove();
return taskInfos;
}
/**
* 触发所有任务执行,并同步等待完成
* @param timeout 最大等待时间
* @param timeUnit 时间单位
* @return 是否在超时前全部完成
*/
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) {
taskInfo.executor.execute(() -> {
try {
taskInfo.runnable.run();
} finally {
latch.countDown();
}
});
}
try {
return latch.await(timeout, timeUnit);
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
return false;
}
}
// 内部任务封装类
private static final class TaskInfo {
private final Executor executor;
private final Runnable runnable;
TaskInfo(Executor executor, Runnable runnable) {
this.executor = executor;
this.runnable = runnable;
}
}
}
- 1.
- 2.
- 3.
- 4.
- 5.
- 6.
- 7.
- 8.
- 9.
- 10.
- 11.
- 12.
- 13.
- 14.
- 15.
- 16.
- 17.
- 18.
- 19.
- 20.
- 21.
- 22.
- 23.
- 24.
- 25.
- 26.
- 27.
- 28.
- 29.
- 30.
- 31.
- 32.
- 33.
- 34.
- 35.
- 36.
- 37.
- 38.
- 39.
- 40.
- 41.
- 42.
- 43.
- 44.
- 45.
- 46.
- 47.
- 48.
- 49.
- 50.
- 51.
- 52.
- 53.
- 54.
- 55.
- 56.
- 57.
- 58.
- 59.
- 60.
- 61.
- 62.
- 63.
- 64.
- 65.
- 66.
- 67.
- 68.
- 69.
- 70.
- 71.
- 72.
- 73.
- 74.
- 75.
- 76.
- 77.
- 78.
- 79.
- 80.
- 81.
- 82.
- 83.
- 84.
- 85.
API 使用说明
| 方法名 | 说明 | 参数 | 返回值 |
|
| 注册一个异步任务 |
| 无 |
|
| 启动所有任务并等待完成 |
|
|
💡 调用
waitFor()后,当前线程的任务列表会被自动清理,可安全重复使用。
实战演示:聚合并行任务的优雅写法
我们以一个聚合服务为例: 主流程需要并行调用 用户服务、订单服务 和 商品服务,在它们全部完成后再继续。
复制
package com.icoderoad.demo;
import com.icoderoad.utils.LatchUtils;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.TimeUnit;
public class Main {
public static void main(String[] args) {
ExecutorService executorService = Executors.newFixedThreadPool(3);
System.out.println("主流程开始,准备分发异步任务...");
// 注册异步任务
LatchUtils.submitTask(executorService, () -> {
System.out.println("开始获取用户信息...");
sleep(1000);
System.out.println("获取用户信息成功!");
});
LatchUtils.submitTask(executorService, () -> {
System.out.println("开始获取订单信息...");
sleep(1500);
System.out.println("获取订单信息成功!");
});
LatchUtils.submitTask(executorService, () -> {
System.out.println("开始获取商品信息...");
sleep(500);
System.out.println("获取商品信息成功!");
});
System.out.println("所有异步任务已提交,主线程开始等待...");
boolean allDone = LatchUtils.waitFor(5, TimeUnit.SECONDS);
if (allDone) {
System.out.println("所有任务执行完成,主流程继续...");
} else {
System.err.println("有任务执行超时,主流程中断!");
}
executorService.shutdown();
}
private static void sleep(long millis) {
try {
Thread.sleep(millis);
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
}
}
}
- 1.
- 2.
- 3.
- 4.
- 5.
- 6.
- 7.
- 8.
- 9.
- 10.
- 11.
- 12.
- 13.
- 14.
- 15.
- 16.
- 17.
- 18.
- 19.
- 20.
- 21.
- 22.
- 23.
- 24.
- 25.
- 26.
- 27.
- 28.
- 29.
- 30.
- 31.
- 32.
- 33.
- 34.
- 35.
- 36.
- 37.
- 38.
- 39.
- 40.
- 41.
- 42.
- 43.
- 44.
- 45.
- 46.
- 47.
- 48.
- 49.
- 50.
- 51.
- 52.
- 53.
- 54.
- 55.
- 56.
- 57.
- 58.
- 59.
- 60.
- 61.
- 62.
- 63.
- 64.
- 65.
- 66.
- 67.
- 68.
运行结果:
复制
主流程开始,准备分发异步任务...
所有异步任务已提交,主线程开始等待...
开始获取商品信息...
开始获取用户信息...
开始获取订单信息...
获取商品信息成功!
获取用户信息成功!
获取订单信息成功!
所有任务执行完成,主流程继续...
- 1.
- 2.
- 3.
- 4.
- 5.
- 6.
- 7.
- 8.
- 9.
传统写法对比
方式一:使用 CountDownLatch(手动控制)
开发者需要显式创建 CountDownLatch,在每个任务中调用 countDown(),并在主线程中调用 await(),代码冗长、易出错。
方式二:使用 CompletableFuture
语义上更现代,但仍需创建多个 Future 并组合等待,异常处理较繁琐。
| 特性 | LatchUtils | CountDownLatch | CompletableFuture |
| 代码简洁度 | 极高 | 中等 | 较高 |
| 状态管理 | 自动 | 手动 | 自动 |
| 异常处理 | 内部封装 | 开发者处理 | 多异常需捕获 |
| 学习曲线 | 低 | 中 | 中 |
| 关注点分离 | 优秀 | 一般 | 良好 |
结语:让并发回归优雅
在高并发系统中,简单往往意味着稳定。LatchUtils 并不是为了替代 JDK 的并发框架,而是为了在特定场景下,提供一种更符合直觉的方式来管理异步任务。
通过 “多次提交,一次等待”,它让开发者只需专注于核心业务逻辑,而不必陷入重复的并发控制细节。
无论是微服务聚合调用、批量任务执行,还是后台数据加载,LatchUtils 都是你 Java 并发工具箱中值得收藏的一件“小而美”的利器。
技术总结:
在并发世界里,最难的不是线程安全,而是保持清晰。 而
LatchUtils的使命,就是让异步编程更简单、更纯粹。
行业拓展
分享一个面向研发人群使用的前后端分离的低代码软件——JNPF。
基于 Java Boot/.Net Core双引擎,它适配国产化,支持主流数据库和操作系统,提供五十几种高频预制组件,内置了常用的后台管理系统使用场景和实用模版,通过简单的拖拉拽操作,开发者能够高效完成软件开发,提高开发效率,减少代码编写工作。
JNPF基于SpringBoot+Vue.js,提供了一个适合所有水平用户的低代码学习平台,无论是有经验的开发者还是编程新手,都可以在这里找到适合自己的学习路径。
此外,JNPF支持全源码交付,完全支持根据公司、项目需求、业务需求进行二次改造开发或内网部署,具备多角色门户、登录认证、组织管理、角色授权、表单设计、流程设计、页面配置、报表设计、门户配置、代码生成工具等开箱即用的在线服务。
351

被折叠的 条评论
为什么被折叠?



