Java 线程池使用 Future,任务没完成啥意思

2022-06-07 17:24:08 +08:00
 frank1256

线程池,核心数 10 个,我循环 2 次,等待第一次 10 个线程结束,无法立刻提交第二次的 10 个线程? 同一时刻只会有 10 个线程跑,咋回事啊,大佬指点迷津 提示的 Not completed,难道 call()方法执行完毕,不算一个任务完成吗

代码如图

报错

finished100
finished100
finished100
finished100
finished100
finished100
finished100
finished100
finished100
finished100
finished one repeate
Exception in thread "main" java.util.concurrent.RejectedExecutionException: Task java.util.concurrent.ExecutorCompletionService$QueueingFuture@48140564[Not completed, task = java.util.concurrent.Executors$RunnableAdapter@7c30a502[Wrapped task = java.util.concurrent.FutureTask@1d251891[Not completed, task = executor.ExecutorDemo$1@49e4cb85]]] rejected from java.util.concurrent.ThreadPoolExecutor@2133c8f8[Running, pool size = 10, active threads = 2, queued tasks = 0, completed tasks = 10]
	at java.base/java.util.concurrent.ThreadPoolExecutor$AbortPolicy.rejectedExecution(ThreadPoolExecutor.java:2055)
	at java.base/java.util.concurrent.ThreadPoolExecutor.reject(ThreadPoolExecutor.java:825)
	at java.base/java.util.concurrent.ThreadPoolExecutor.execute(ThreadPoolExecutor.java:1355)
	at java.base/java.util.concurrent.ExecutorCompletionService.submit(ExecutorCompletionService.java:184)
	at executor.ExecutorDemo.main(ExecutorDemo.java:34)


2789 次点击
所在节点    Java
25 条回复
wbd31
2022-06-07 19:03:56 +08:00
线程池的核心线程是不受 keepAliveTime 参数控制的,所以 0-10 和 11-20 都会被同一批核心线程执行。
因为 workQueue 设置的容量是 1 ,当第二批任务开始执行的时候,先插入到 workQueue, 此时核心线程执行完了自己的 firstTask 后会一直轮询 getTask 获取 workQueue 里的任务,但是任务里 sleep 了 1s 导致消费速度小于第二批任务的 submit 速度(其实就算不 sleep 也有可能发生)导致 submit 的时候进入到了 addWorker 的判断,由于已经达到了最大线程数量,不再新建线程,走了 reject
frank1256
2022-06-08 09:03:14 +08:00
@wbd31 了解了
byte10
2022-06-08 11:19:00 +08:00
因为核心线程数设置了 10 ,跟最大线程数 10 一样,因为内置的线程池实现是默认不初始化核心线程数的( apache 默认初始化核心线程数),所以第一次时候,全部使用创建新线程执行 10 个任务。

到了第二次之后,任务来了之后默认存放在队列中(因为 corePoolSize 已经满了),但是这个时候太快了,线程池还没来得及消费这个队列任务(还没拿走这个任务),就满了(而且最大线程数也等于 10 ,也满了),导致触发拒绝策略。我也纳闷为何不直接拿核心线程去执行呢?因为去拿核心线程执行,还需要判断这个核心线程是否空闲,那不如直接丢到任务队列里,核心线程有空闲就直接去拿就可以了。

尝试调整参数,调整 corePoolSize = 7 或者 8 , maximumPoolSize=10 不变,还是不行。前面 10 个任务正常处理,但是到了第 14, 15 个依然不行。虽然 keepAliveTime = 0, 但是还没来得及释放线程,就会出现问题队列塞满的情况,corePoolSize = 0 或者 1 ,就可以正常执行,因为来得及释放线程,后续的任务依然可以新创建线程来执行。

这种极端的测试还是挺有意思的。可以先了解下 线程池原理,因为很快找到问题。
byte10
2022-06-08 11:33:15 +08:00
@frank1256 对了 解决的办法是:任务等待队列适当增加,一般按能接受的任务执行时长来设置。如果很大,意味着后续的任务可能要排队很久才会被执行到,好比去银行排队,前面都有 1000 人 ,实在没必要排队。这个时候应该抛出异步,早点暴露出当前线程池不足的问题。而你的这种情况 新增任务太快,可以适当延迟 1-3ms 的新增间隔,让线程池有时间去取队列中的任务即可,或者设置队列长度 5-10 即可。你这种情况毕竟比较极端的测试,实际场景较少。keepAliveTime 也还好,10-60s 都可以,跟任务并发吞吐量 的变化有一些关系,一般不是特别需要关注。0 的话太极端了。
nothingistrue
2022-06-08 12:34:41 +08:00
我看了回复才发现你任务队列长度是 1 ,那一次性提交 10 个任务,出错是正常的,不出错才有问题。

这个 ThreadPoolExector ,Executor 执行器才是关键,ThreadPool 线程池只是执行器的一种实现方式(虽然 Java 就这一种实现方式,但其他语言会有其他方式)。

然后你的问题,你已经回答了,call()方法执行完毕,确实不算一个任务完成。service.take()执行完成后,可以确定 call()方法执行完了,通常也代表任务执行完了,但是执行器内部有没有把任务标记为完成,就不好说了,一般会有少量的延迟。

最后,我看了一下你的错误信息,里面有这么一句:

.ThreadPoolExecutor@2133c8f8[Running, pool size = 10, active threads = 2, queued tasks = 0, completed tasks = 10]

注意这个 active threads = 2 ,第一个 10 个任务完成之后,执行器有可能做优化了,就留 2 个线程,毕竟你队列长度是 1 ,2 个线程足够了。

这是一个专为移动设备优化的页面(即为了让你能够在 Google 搜索结果里秒开这个页面),如果你希望参与 V2EX 社区的讨论,你可以继续到 V2EX 上打开本讨论主题的完整版本。

https://www.v2ex.com/t/857959

V2EX 是创意工作者们的社区,是一个分享自己正在做的有趣事物、交流想法,可以遇见新朋友甚至新机会的地方。

V2EX is a community of developers, designers and creative people.

© 2021 V2EX