阅读Java源码之ThreadPoolExecutor

前言

在使用Java的任务管理框架执行任务的过程中,饱和策略在任务等待队列已满并且提交新任务时起作用。 ThreadPollExecutor提供了四种饱和策略:

阅读Java源码之ThreadPoolExecutor

 

我们在上一节中介绍了它们的源代码。本节将看到它们之间的区别。 

测试类准备

首先定义MyCommand任务并接收字符串消息:

阅读Java源码之ThreadPoolExecutor

 

创建一个集成测试类。线程池的初始化大小为2、等待队列大小为2、如果提交的任务大于4、则由于不同的饱和度策略,第五个任务将获得不同的执行结果。在下一篇文章中,我们将设置不同的饱和度策略并测试行为上的差异。 

阅读Java源码之ThreadPoolExecutor

 

 

策略1:放弃通知模式

AbortPolicy是默认的饱和策略。该策略引发未经检查的异常RejectedExecutionException。调用者可以捕获此异常并根据需要编写代码。例如,尝试捕获异常并重新发送任务。该策略仍然很友好,并且至少在销毁任务之前会通知任务发送者。这是默认策略,因此,如果直接运行测试准备类的第一部分,结果将是:

阅读Java源码之ThreadPoolExecutor

 

测试结果:主线程发送4个任务后,队列已满。此时,提交第五项任务时,线程池引发了RejectedExecutionException,并且主线程能够捕获并处理该异常。 

策略2:以静默方式丢弃

DiscardPolicy,并以静默方式接受任务,但是在异常情况下不执行任何操作,并且调用方不知道任务的状态。

显然,这对任务控制没有帮助,因此我不推荐这种策略。

测试结果:发送了5个线程,仅执行了4个任务,最后提交的任务在调用者不知情的情况下被无情地抛弃了。

阅读Java源码之ThreadPoolExecutor

策略3:销毁等待时间最长的任务

DiscardOldestPolicy丢弃等待队列中等待时间最长的任务,将其从队列中删除,然后运行当前任务。这不适合等待时间最长的任务。

更改测试策略:

exec.setRejectedExecutionHandler(new ThreadPoolExecutor.DiscardOldestPolicy());

测试结果:发送了5个线程,但是仅执行了4个任务。最长的等待任务c3被销毁,呼叫者无法得知。

阅读Java源码之ThreadPoolExecutor

 

策略4:调用者执行

CallerRunsPolicy策略以提供协调机制。它不会放弃任务或引发异常,但会将任务的运行请求返回给任务的调用者,并且发送任务的线程会自行执行。任务已发送。

测试结果:发送了五个线程,但辅助线程仅执行了四个,而第五个任务由调用者执行。

阅读Java源码之ThreadPoolExecutor

 

结论:调用者执行的饱和策略实现了弹性调整机制。当工作队列已满时,下一个要执行的任务将在发送任务的主线程中执行。

当主线程执行任务时,线程资源被占用,无法发送其他任务。因此,任务提交率降低,线程池花费更多时间来完成排队的任务。

策略5:调用方限制

前四个传输,这是线程池饱和策略。另外,任务发送者可以控制任务的发送速率。即,限制任务的发送以避免任务饱和。例如,通过使用信号量来限制任务的到达速度,此同步工具类可以控制同时访问特定资源的操作数。您可以使用“获取信号量”来获取虚拟许可证。如果没有可用的许可证,则方法调用线程将被阻塞,直到可用的许可证为止。如果线程池使用无限队列来缓冲任务并且不控制任务数量,则很可能导致内存耗尽。然后,您可以在信号量中使用它来设置信号量上限并控制任务提交速度。将上一章中的MyCommand任务与Semaphore结合使用,以实现调用方控制任务发送的示例。

任务执行结果:

阅读Java源码之ThreadPoolExecutor

 

分析执行结果:使用semaphos将一次可以发送的任务数限制为只有两个。任务完成后,将释放信号灯权限,您可以有效地控制任务的提交速度。 

总结

Apocalypse的受限线程池的四种饱和策略中,只有AbortPolicy和CallerRunPolicy对任务发送者很友好,而其他的则错过了任务。这对于任务发送者是不利的。折衷方案是调用方控制任务的发送速度,并使用semaphoe根据线程池的配置大小来控制任务的发送。这将防止过多的任务提交。

上一篇:线程池源码分析-ThreadPoolExecutor


下一篇:[Java] Java核心(2)深入理解线程池ThreadPool