使用 ScheduledThreadPoolExecutor 允许核心线程超时
Allowing core thread timeout with ScheduledThreadPoolExecutor
你能解释一下吗,为什么在 ScheduledThreadPoolExecutor
javadoc 中是这样的:
Additionally, it is almost never a good idea to set corePoolSize
to
zero or use allowCoreThreadTimeOut
because this may leave the pool without
threads to handle tasks once they become eligible to run.
我已经尝试分析当必须执行新任务时如何在此线程池中创建新线程,我认为 javadoc 中描述的问题不应该发生。
线程池尽量使工作线程数等于corePoolSize
,通过缓存线程来提高效率。允许核心线程超时是违背这个目的的。如果允许核心线程超时,会执行新任务,但会导致重复创建和销毁工作线程。
如果设置allowCoreThreadTimeOut = true
,则工作线程发现任务队列中没有任务超时后,即使工作线程数少于corePoolSize
也会被销毁。所以,如果此时提交新的任务,线程池就得创建新的线程。
如果设置allowCoreThreadTimeOut = false
,则工作线程发现任务队列中没有任务且工作线程数小于corePoolSize
后,不会被销毁,继续等待新任务.
我的猜测是大部分答案都是陈旧的 Javadoc。正如您所注意到的,ensurePrestart
确保只要 corePoolSize
> 0,核心池线程数在调用后就不为零。自 https://github.com/openjdk/jdk/commit/2d19ea519b17529a083a62eb219da532693bbef3 以来就是这种情况,但值得注意的是,该提交并未更新 ScheduledThreadPoolExecutor
.
上的 Javadoc
然而,细节也不是那么简单。不仅要担心新任务的提交和任务完成时的重新安排,您还需要担心核心池线程空闲,因为所有计划的任务都在未来太远,无法在池超时之前触发。
不确定我是否正确阅读了 JRE 代码,但看起来在这种情况下,池将:
- 启动一个工作线程(由于
ensurePrestart
)
- 无论线程类型(核心与否),线程都符合超时条件,因为
allowCoreThreadTimeout
为真
- 工作线程轮询
DelayedWorkQueue
下一个任务,超时
- poll returns
null
(超时),因为下一个定时任务超出pool timeout
- 核心线程会终止,因为它认为无事可做
ThreadPoolExecutor.processWorkerExit
将 运行 终止工人。它将检查队列,注意它是非空的,因此至少需要一个线程
- 如果被终止的线程是最后一个线程,它会注意到没有满足最小值并立即启动一个新的(非核心)worker
- 从第 1 步开始重复
因此,池将按您的预期工作,但可能也不会处于理想状态(您真的想要一个没有超时的单核线程轮询,而不是线程不断旋转,超时轮询,超时,然后启动一个新线程来替换自身)。从这个意义上说,第 6 步确实可以防止 Javadoc 中提到的情况(当任务符合条件时,所有池线程都已超时),但由于不必要的线程 creation/destruction循环。
这种怪异实际上是因为 DelayedWorkQueue
在语义上违反了 BlockingQueue
契约。在其他条件相同的情况下,您会假设 size() > 0
意味着后续的 poll(...)
将成功检索元素并且不会超时,但是 DelayedWorkQueue
允许这些元素被保留一段时间(即使它们已经通过 size()
和 isEmpty()
).
可见
注意:似乎此代码在 Java 8 中有所更改,在 ThreadPoolExecutor.getTask()
中添加了 this condition。这将使最后一个工作线程保持活动状态并避免线程 create/destroy 循环,但它会忙于轮询工作队列以进行工作。
你能解释一下吗,为什么在 ScheduledThreadPoolExecutor
javadoc 中是这样的:
Additionally, it is almost never a good idea to set
corePoolSize
to zero or useallowCoreThreadTimeOut
because this may leave the pool without threads to handle tasks once they become eligible to run.
我已经尝试分析当必须执行新任务时如何在此线程池中创建新线程,我认为 javadoc 中描述的问题不应该发生。
线程池尽量使工作线程数等于corePoolSize
,通过缓存线程来提高效率。允许核心线程超时是违背这个目的的。如果允许核心线程超时,会执行新任务,但会导致重复创建和销毁工作线程。
如果设置allowCoreThreadTimeOut = true
,则工作线程发现任务队列中没有任务超时后,即使工作线程数少于corePoolSize
也会被销毁。所以,如果此时提交新的任务,线程池就得创建新的线程。
如果设置allowCoreThreadTimeOut = false
,则工作线程发现任务队列中没有任务且工作线程数小于corePoolSize
后,不会被销毁,继续等待新任务.
我的猜测是大部分答案都是陈旧的 Javadoc。正如您所注意到的,ensurePrestart
确保只要 corePoolSize
> 0,核心池线程数在调用后就不为零。自 https://github.com/openjdk/jdk/commit/2d19ea519b17529a083a62eb219da532693bbef3 以来就是这种情况,但值得注意的是,该提交并未更新 ScheduledThreadPoolExecutor
.
然而,细节也不是那么简单。不仅要担心新任务的提交和任务完成时的重新安排,您还需要担心核心池线程空闲,因为所有计划的任务都在未来太远,无法在池超时之前触发。
不确定我是否正确阅读了 JRE 代码,但看起来在这种情况下,池将:
- 启动一个工作线程(由于
ensurePrestart
) - 无论线程类型(核心与否),线程都符合超时条件,因为
allowCoreThreadTimeout
为真 - 工作线程轮询
DelayedWorkQueue
下一个任务,超时 - poll returns
null
(超时),因为下一个定时任务超出pool timeout - 核心线程会终止,因为它认为无事可做
ThreadPoolExecutor.processWorkerExit
将 运行 终止工人。它将检查队列,注意它是非空的,因此至少需要一个线程- 如果被终止的线程是最后一个线程,它会注意到没有满足最小值并立即启动一个新的(非核心)worker
- 从第 1 步开始重复
因此,池将按您的预期工作,但可能也不会处于理想状态(您真的想要一个没有超时的单核线程轮询,而不是线程不断旋转,超时轮询,超时,然后启动一个新线程来替换自身)。从这个意义上说,第 6 步确实可以防止 Javadoc 中提到的情况(当任务符合条件时,所有池线程都已超时),但由于不必要的线程 creation/destruction循环。
这种怪异实际上是因为 DelayedWorkQueue
在语义上违反了 BlockingQueue
契约。在其他条件相同的情况下,您会假设 size() > 0
意味着后续的 poll(...)
将成功检索元素并且不会超时,但是 DelayedWorkQueue
允许这些元素被保留一段时间(即使它们已经通过 size()
和 isEmpty()
).
注意:似乎此代码在 Java 8 中有所更改,在 ThreadPoolExecutor.getTask()
中添加了 this condition。这将使最后一个工作线程保持活动状态并避免线程 create/destroy 循环,但它会忙于轮询工作队列以进行工作。