Java 并发神器:LatchUtils 一招搞定复杂异步同步!

79 阅读6分钟

在高并发系统中,简单往往意味着稳定。LatchUtils 并不是为了替代 JDK 的并发框架,而是为了在特定场景下,提供一种更符合直觉的方式来管理异步任务。

在现代 Java 应用开发中,并发几乎是提升性能的“标配”。无论是并行调用多个微服务接口、批量查询数据库,还是执行密集计算,我们都离不开异步与并行。

然而,当主线程需要等待多个异步任务完成后再继续执行时,开发者往往需要手动编写大量控制逻辑。例如使用 ExecutorServiceCountDownLatchCompletableFuture 等工具时,总会出现冗余的样板代码(如 latch.countDown()、异常处理、await 等)。

这些重复性逻辑不仅分散了业务重点,还让代码显得笨重。

于是,我们引入了一个轻量级的并发封装工具 —— **LatchUtils**。 它秉持“多次提交,一次等待”的核心设计理念,让异步任务的管理变得极其简洁。

设计思想:多次提交,一次等待

LatchUtils 的核心思想是将任务注册与任务等待分离:

  • 任务注册阶段:通过 submitTask() 方法注册多个任务及其对应线程池;
  • 等待阶段:在所有任务提交完成后,调用一次 waitFor() 即可触发执行并同步等待。

简单来说,你只需要两步:

  1. 提交所有异步任务
  2. 等待全部执行完毕

而不需要再关心 CountDownLatch 的创建、计数或中断异常。

项目路径结构

复制

/src
 └── main
     └── java
         └── com
             └── icoderoad
                 └── utils
                     └── LatchUtils.java1.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 使用说明

方法名

说明

参数

返回值

submitTask(Executor, Runnable)

注册一个异步任务

executor:执行线程池runnable:任务逻辑

waitFor(long, TimeUnit)

启动所有任务并等待完成

timeout:超时时间timeUnit:时间单位

true - 全部成功false - 超时

💡 调用 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支持全源码交付,完全支持根据公司、项目需求、业务需求进行二次改造开发或内网部署,具备多角色门户、登录认证、组织管理、角色授权、表单设计、流程设计、页面配置、报表设计、门户配置、代码生成工具等开箱即用的在线服务。