写点什么

图解定时任务线程池

用户头像
叫练
关注
发布于: 2021 年 02 月 25 日
图解定时任务线程池

线程池概念


我们上篇文章分析了 ThreadPoolExecutor,如果要用一句话说明它的主要优势,就是线程置换。还有 Executors 工具类,极大的简化了研发人员工作。

我用一个图重复描述下线程池概念。多生产-多消费模型。


image.png


  • 生产者将线程任务丢进线程池中,生产者就就结束了。

  • 线程池控制消费者消费元素,消费者可以是 1 个或者多个,取决于线程池参数 corePoolSize 和 maxPoolSize 设置。

  • 阻塞队列是用来装生产者丢进去的线程任务,如 ArrayBlockingQueue,LinkedBlockingQueue,DelayedQueue 等。如果生产者生产能力超过消费者消费能力,如果阻塞队列有长度限制并且超过队列长度线程池会执行饱和策略,如果队列没有长度限制,可也能出现 OOM 哦,因为线程任务可能把内存都撑爆了,这也是面试常考点哦!

详细概念可以翻看我上一篇文章《线程池面试必考问题》。


定时任务延时原理


还记得我们上面说的阻塞队列吗?定时任务线程池底层使用 DelayedQueue 实现的,这种延迟队列有一个最大的特点:按时出队列,大家都考过驾照吧,科目三考试的时候都是车上坐的是 4 个人,假设一个人考试需要花 15 分钟,那么考试学员队列看起来是这样的。


image.png


DelayedQueue 底层需要实现 Delayed 接口同时需要实现 getDelay 方法和 compareTo 方法,getDelay 方法用于计算出队列时间,一旦小于 0 就会出队列;compareTo 方法用于按触发时间从小到大排序。这就是 Schedule 线程池任务延时原理,如果需要看案例代码,请参考我文章《并发队列:PriorityBlockingQueue 和 DelayQueue 案例使用》。


scheduleWithFixedDelay 和 scheduleAtFixedRate 区别



image.png


由上图可知:假设线程任务:耗时 1 秒,定时 3 秒执行,scheduleWithFixedDelay 其实是 4 秒执行一次。

  • scheduleWithFixedDelay:是以任务结束时间周期运行。

  • scheduleAtFixedRate:是以固定周期运行。


FutureTask 获取返回值


在 ScheduledThreadPoolExecutor 中,ScheduledFutureTask 是获取定时任务返回值,继承 FutureTask。我们看下 FutureTask 调用 get 阻塞简化流程图。


image.png


  1. 向线程池添加任务,任务被封装成 ScheduledFutureTask 并且实现 Callable 接口是为了获取任务返回值。

  2. 当客户端线程通过 get 方式获取线程池线程执行的返回值,客户端线程会阻塞直到线程池线程任务执行完。

在实际运用,我们一般拿返回值测试多线程性能。


Timer 比较


  1. Timer 是单线程,而且不带返回值。ScheduledThreadPoolExecutor 是多线程的,采用线程复用代替创建新的线程,并且 FutureTask 带返回值。

  2. Timer 线程调用 sche 方法,如果 TimerTask 出异常,Timer 单线程直接挂掉退出,而 ScheduledThreadPoolExecutor 会捕获了异常,不影响其他消费者线程。下面是代码测试。

import java.util.Timer;import java.util.TimerTask;import java.util.concurrent.TimeUnit;
/** * @author :jiaolian * @date :Created in 2021-02-25 13:50 * @description:Timer任务异常,Timer线程退出! * @modified By: * 公众号:叫练 */public class TimerTaskExceptionTest {
public static void main(String[] args) throws InterruptedException {
Timer timer = new Timer(); timer.schedule(new TimerTask() { @Override public void run() { System.out.println("first_task"); } },0,1000);
TimeUnit.SECONDS.sleep(3); timer.schedule(new TimerTask() { @Override public void run() { System.out.println("second_task"); int x = 5/0; } },0,1000);
}}
复制代码

如上代码:timer 提交了 first_task 和 second_task 两个任务,3 秒后,second_task 执行 5/0 会抛出异常,此时 timer 线程会退出。如果换成 ScheduledThreadPoolExecutor 则不会影响 first_task。在实际应用中,推荐用定时任务线程池中的方法去处理任务。


总结


今天我们介绍了线程池中面试中几个重要的面试点,整理出来希望能对你有帮助,写的比不全,同时还有许多需要修正的地方,希望亲们加以指正和点评,喜欢的请点赞加关注哦。点关注,不迷路,我是叫练【公众号】,微信号【jiaolian123abc】边叫边练。


发布于: 2021 年 02 月 25 日阅读数: 18
用户头像

叫练

关注

我是叫练,边叫边练 2020.06.11 加入

Java高级工程师,熟悉多线程,JVM

评论

发布
暂无评论
图解定时任务线程池