Java主线程外异步计算是否每次需新建ExecutorService?与JS差异原因?
Java CompletableFuture 异步执行问题解答
问题相关代码示例
示例1:自定义ExecutorService的异步代码
public void run (String[] args) throws Exception { System.out.println("testing async"); ExecutorService ex = Executors.newFixedThreadPool(10); CompletableFuture.supplyAsync(()-> { return secondfunc(""); }, ex).thenApply( (val)-> { return secondfunc(val); }).thenApply( (val) -> { return thirdfunc(val); }).thenAccept((val)-> { System.out.println(val); ex.shutdown(); }); System.out.println("done testing async"); }
示例1输出
testing async done testing async 200 200 -->println from fisrt method 200 200 -->println from second method 200 200 -->println from third method Done Post twoDone Post twoDone Post three -->result on the accept
示例2:使用默认线程池的异步代码
public void run (String[] args) throws Exception { System.out.println("testing async"); CompletableFuture.supplyAsync(()-> { return secondfunc(""); }).thenApply( (val)-> { return secondfunc(val); }).thenApply( (val) -> { return thirdfunc(val); }).thenAccept((val)-> { System.out.println(val); }); System.out.println("done testing async"); }
示例2输出
testing async done testing async
问题解答
1. Java执行主线程外的异步计算是否每次都需要创建ExecutorService?
不需要:
- CompletableFuture的
supplyAsync、runAsync等方法如果不传Executor参数,会默认使用JDK自带的全局公共线程池ForkJoinPool.commonPool(),无需每次自行创建线程池。 - 示例2没有输出后续异步任务日志的原因是:公共ForkJoinPool里的线程默认都是守护线程,JVM进程终止的条件是所有非守护线程全部执行结束,示例2中主线程(非守护线程)执行完最后一行打印后直接退出,JVM不会等待守护线程里的异步任务执行完成,所以后续日志没有输出。
- 生产环境更建议自定义ExecutorService而非使用公共池:公共池是全局共享的,多个业务共用容易出现资源争抢、互相影响的问题,自定义线程池可以灵活配置线程数、线程名、异常处理策略、拒绝策略等,也可以自主控制线程是否为守护线程。同时自定义线程池支持全局复用,不需要每次执行异步任务都新建。
2. 该场景下Java的异步表现为什么和JavaScript存在差异?
二者的异步运行模型完全不同:
- Java的异步基于多线程实现,JVM进程的生命周期和非守护线程绑定,主线程退出后如果没有其他非守护线程运行,进程会直接终止,未完成的守护线程任务会被直接中断丢弃。
- JavaScript是单线程事件循环模型,只要异步任务还在等待执行(比如定时器、IO回调、Promise回调未触发),事件循环就不会停止,进程(Node.js场景)或者页面上下文(浏览器场景)就不会主动退出,除非手动终止进程/关闭页面,所以JS里的异步任务只要没有报错,基本都会执行完成。
内容的提问来源于stack exchange,提问作者Leandro Gamarra
相关产品推荐
相关产品推荐

