为什么Java 17向ForkJoinPool添加任务时会抛出RejectedExecutionException
异常触发原因
这个问题确实是Java 17对ForkJoinPool的逻辑调整导致的,核心差异点在补偿线程的创建限制上:
- 首先要明确,你代码里调用的
HttpClient.send()是同步阻塞方法,它的内部实现依赖CompletableFuture.get(),而这个方法实现了ForkJoinPool的ManagedBlocker接口。当ForkJoinPool的工作线程执行到这类阻塞逻辑时,池会自动尝试创建补偿线程,保证整体并行度不会因为线程阻塞下降。 - Java 16的
ForkJoinPool存在逻辑漏洞:计算可创建的补偿线程数时没有严格校验maximumPoolSize上限,哪怕你设置的最大线程数已经满了,还是能创建额外的补偿线程,所以你设置threads=3的时候也能正常运行。 - Java 17修复了这个漏洞,补偿线程的创建会严格遵守你构造
ForkJoinPool时传入的maximumPoolSize参数。你的代码里把maximumPoolSize和并行度parallelism都设为同一个值threads,当threads=3时,3个并行的HTTP请求任务全部阻塞在send方法,池需要创建补偿线程但已经触达最大线程数限制,就会抛出RejectedExecutionException。而threads=4的时候最大线程数足够容纳补偿线程,所以两个版本都能正常运行。
修复方案
- 调整
ForkJoinPool构造参数:不要把maximumPoolSize和parallelism设为相同值,根据业务场景预留足够的补偿线程空间,比如并行度设为3的话,最大线程数可以设为10甚至更高。 - 改用异步IO:调用
HttpClient.sendAsync()方法替换同步的send(),避免阻塞ForkJoinPool的工作线程,自然不会触发补偿线程创建逻辑。 - 更换线程池实现:
ForkJoinPool本身是为CPU密集型任务设计的,IO密集型的HTTP请求场景更适合用ThreadPoolExecutor实现,不会有补偿线程的限制问题。
内容的提问来源于stack exchange,提问作者TTT
相关产品推荐
相关产品推荐

