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

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

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

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

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

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

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

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

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

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

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

而不需要再关心 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 使用说明

方法名

说明

参数

返回值

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

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值