并发编程从零开始(十四)-Executors工具类

并发编程从零开始(十四)-Executors工具类

12 Executors工具类

concurrent包提供了Executors工具类,利用它可以创建各种不同类型的线程池

12.1 四种对比

单线程的线程池:

并发编程从零开始(十四)-Executors工具类_第1张图片

固定数目线程的线程池:

并发编程从零开始(十四)-Executors工具类_第2张图片

每接收一个请求,就创建一个线程来执行:

并发编程从零开始(十四)-Executors工具类_第3张图片

单线程具有周期调度功能的线程池:

并发编程从零开始(十四)-Executors工具类_第4张图片

多线程,有调度功能的线程池:

image-20211102172315063


12.2 最佳实践

不同类型的线程池,其实都是由前面的几个关键配置参数配置而成的。

在《阿里巴巴Java开发手册》中,明确禁止使用Executors创建线程池,并要求开发者直接使用ThreadPoolExector或ScheduledThreadPoolExecutor进行创建。这样做是为了强制开发者明确线程池的运行策略,使其对线程池的每个配置参数皆做到心中有数,以规避因使用不当而造成资源耗尽的风险。


13 ScheduledThreadPoolExecutor

ScheduledThreadPoolExecutor实现了按时间调度来执行任务:

1. 延迟执行任务

并发编程从零开始(十四)-Executors工具类_第5张图片

并发编程从零开始(十四)-Executors工具类_第6张图片

2. 周期执行任务

并发编程从零开始(十四)-Executors工具类_第7张图片

并发编程从零开始(十四)-Executors工具类_第8张图片

区别如下:

AtFixedRate:按固定频率执行,与任务本身执行时间无关。但有个前提条件,任务执行时间必须小于间隔时间,例如间隔时间是5s,每5s执行一次任务,任务的执行时间必须小于5s。

WithFixedDelay:按固定间隔执行,与任务本身执行时间有关。例如,任务本身执行时间是10s,间隔2s,则下一次开始执行的时间就是12s。


13.1 延迟执行和周期性执行的原理

ScheduledThreadPoolExecutor继承了ThreadPoolExecutor,这意味着其内部的数据结构和ThreadPoolExecutor是基本一样的,那它是如何实现延迟执行任务和周期性执行任务的呢?

延迟执行任务依靠的是DelayQueue。DelayQueue是 BlockingQueue的一种,其实现原理是二叉堆。

而周期性执行任务是执行完一个任务之后,再把该任务扔回到任务队列中,如此就可以对一个任务反复执行。

不过这里并没有使用DelayQueue,而是在ScheduledThreadPoolExecutor内部又实现了一个特定的DelayQueue

image-20211102183205357

其原理和DelayQueue一样,但针对任务的取消进行了优化。下面主要讲延迟执行和周期性执行的实现过程。


13.2 延迟执行

并发编程从零开始(十四)-Executors工具类_第9张图片

传进去的是一个Runnable,外加延迟时间delay。在内部通过decorateTask(...)方法把Runnable包装成一个ScheduleFutureTask对象,而DelayedWorkQueue中存放的正是这种类型的对象,这种类型的对象一定实现了Delayed接口。

并发编程从零开始(十四)-Executors工具类_第10张图片

并发编程从零开始(十四)-Executors工具类_第11张图片

从上面的代码中可以看出,schedule()方法本身很简单,就是把提交的Runnable任务加上delay时间,转换成ScheduledFutureTask对象,放入DelayedWorkerQueue中。任务的执行过程还是复用的ThreadPoolExecutor,延迟的控制是在DelayedWorkerQueue内部完成的。


13.4 执行周期

并发编程从零开始(十四)-Executors工具类_第12张图片

并发编程从零开始(十四)-Executors工具类_第13张图片

和schedule(...)方法的框架基本一样,也是包装一个ScheduledFutureTask对象,只是在延迟时间参数之外多了一个周期参数,然后放入DelayedWorkerQueue就结束了。

两个方法的区别在于一个传入的周期是一个负数,另一个传入的周期是一个正数,为什么要这样做呢?

用于生成任务序列号的sequencer,创建ScheduledFutureTask的时候使用:

image-20211102183456120

并发编程从零开始(十四)-Executors工具类_第14张图片

并发编程从零开始(十四)-Executors工具类_第15张图片

withFixedDelay和atFixedRate的区别就体现在setNextRunTime里面。

如果是atFixedRate,period>0,下一次开始执行时间等于上一次开始执行时间+period;

如果是withFixedDelay,period < 0,下一次开始执行时间等于triggerTime(-p),为now+(-period),now即上一次执行的结束时间。

你可能感兴趣的:(并发编程从零开始(十四)-Executors工具类)