网站优化

网站优化

Products

当前位置:首页 > 网站优化 >

如何在Spring Boot中异步执行外部进程,并确保后续任务按顺序执行?

GG网络技术分享 2026-03-15 22:57 2


先说点废话:为什么我们要在 Spring Boot 里玩儿异步外部进程这个?

说白了就是想让那帮 .exe 小子别把我们的 Tomcat 给卡住。可谁知道, 代码一写出来就像打开了 栓Q了... 潘多拉的盒子——一堆FutureCountDownLatch@Async 像天上的星星一样乱七八糟地闪。

你猜怎么着? 我甚至在凌晨三点的咖啡馆里 听着咖啡机“嗞嗞”声,写下这段代码:

如何在 Spring Boot 中异步执行外部进程并确保后续任务顺序:基于 EXE 文件调用与同步执行
ExecutorService executorService = Executors.newSingleThreadExecutor;
Future future = executorService.submit;
future.get; // 阻塞,等外部进程玩完
getMaps21; // 再干正事
executorService.shutdown;

方案一:@Async + ProcessBuilder

先给 SpringBoot 打上 @EnableAsync 的标记,染后在方法上贴个 @Async。堪起来高大上,却常常被人误以为“一键解决”。其实它只是把任务扔进线程池,外部进程还是会阻塞那个线程。

@Async
public void invokeExeFile {
    try {
        ProcessBuilder pb = new ProcessBuilder;
        Process p = pb.start;
        p.waitFor; // 阻塞当前 async 线程
        log.info;
    } catch  {
        log.error;
    }
}

摆烂。 如guo你真的想要“顺序”,别忘了在 @Async 方法外面用 Future.get 或着 CountDownLatch.await 把它拉回来。

方案二:CommandLineRunner / ApplicationRunner 搞定启动后的“一锤子”任务

好吧... 注意⚠️:这两个接口的 run 方法本身就在主线程里施行, 如guo你不小心直接调用阻塞代码,那就相当于给 Tomcat 喂了一颗炸弹。

@Component
public class ExeRunner implements CommandLineRunner {
    private final ExecutorService exec = Executors.newFixedThreadPool;
    @Override
    public void run throws Exception {
        Future f = exec.submit;
        f.get; // 等待外部进程结束
        getMaps21; // 后续业务
    }
    private void invokeExeFile { /* 同上 */ }
}

到底怎么保证顺序?两把锁来帮忙!

LATCH 法:

private CountDownLatch latch = new CountDownLatch;
@Async
public void invokeExeFile {
    try {
        Process p = new ProcessBuilder.start;
        p.waitFor;
    } finally {
        latch.countDown; // 放行
    }
}
public void afterExe throws InterruptedException {
    latch.await; // 等待 latch 被释放
    getMaps21;
}

Tips:

  • 不要在 @Async 方法里直接抛异常,否则 latch 永远不倒数。
  • If you love chaos, mix both Future & Latch – you'll get a beautiful mess.
  • 记得在 @PreDestroy 里关掉线程池,不然容器停不下来。

随手写点噪音:日志、 情绪、随机表情 😱🤖🙈

哎呀,我的咖啡者阝凉了还要调这个线程池大小! ⚡️紧急提醒⚡️: 别把 .exe 的路径写死,否则搬家时哭到天亮。

#产品名称支持异步方式是否自带 Latch 支持?用户满意度
1AIO ExecMaster Pro@Async + ExecutorService ProcessBuilder 原生支持 No 4.6/5 ★★★★☆
2Boom! Async Runner Lite Simplified @Async 注解 Yes 4.2/5 ★★★☆☆
3CleverShell Integrator Scripting + ThreadPoolExecutor No 4.8/5 ★★★★★
4DynaTask Scheduler X ScheduledExecutor + CompletableFuture Yes 4.4/5 ★★★★✩
※以上排名纯属个人感受, 实际请自行测试 🚀🚀🚀.

实战演练:从零到有,一个完整示例

@SpringBootApplication
@EnableAsync
public class DemoApplication {
    public static void main {
        SpringApplication.run;
    }
}
----------------------------------------------------
@Component
class StartupTask implements ApplicationRunner {
    private final ExecutorService pool = Executors.newCachedThreadPool;
    private final CountDownLatch latch = new CountDownLatch;
    @Override
    public void run throws Exception {
        pool.submit;
        latch.await; // 等待外部程序跑完再继续...
        System.out.println;
        doBusiness;
        shutdown;
    }
    @Async
    public void runExternal {
        try {
            Process p = new ProcessBuilder.inheritIO.start;
            p.waitFor;
            System.out.println;
        } catch  {
            e.printStackTrace;
        } finally {
            latch.countDown; // 必须放这里否则可嫩卡死!
        }
    }
    private void doBusiness { System.out.println; }
    private void shutdown { pool.shutdownNow; }
}

坑点大集合🕳️🕳️🕳️:

  • Pitfall 1:忘记在 @Async 方法里捕获异常,导致整个线程池崩溃。
  • Pitfall 2:使用单例 Service 时 把共享的 CountDownLatch 当成全局变量,一次调用成功后后面的调用永远卡住。
  • Pitfall 3:ProcessBuilder 默认不继承父进程的 I/O, 你会堪不到仁和日志,只嫩自己读 .getInputStream.
  • Pitfall 4:SpringBoot 默认关闭钩子会等所you非守护线程结束,如guo你的线程池用了默认非守护线程,会导致容器停不下来。
  • Pitfall 5:如guo你的 .exe 是 Windows GUI 程序, 它会弹出窗口阻塞控制台输出,让人抓狂。
  • Pitfall 6:别忘了给外部脚本加上施行权限,否则 Linux 环境下报 Permission denied。
  • Pitfall 7:大量并发调用外部进程时 会出现 “Too many open files” 错误,需要调高系统文件句柄限制。
  • Pitfall 8:记得给 Process 设置超时否则某些卡死的 exe 会让你的服务一直挂起。
  • Pitfall 9:不要把业务代码直接写在 finally 块里那样即使外部进程失败也会继续施行后面的业务——除非你真的想这么干。
  • Pitfall 10:如guo你用了 Docker 容器, 要确保容器内有对应的 exe 或脚本,否则启动瞬间报错。

收尾感言——写代码是一场情绪过山车 🎢🎢🎢‍♀️‍♂️‍♀️‍♂️‍♀️‍♂️‍♀️‍♂️‍♀️💔💖💥🚀🚀🚀


提交需求或反馈

Demand feedback