定时线程池原理解析

news2024/10/6 22:23:37

基本使用

ScheduledThreadPoolExecutor继承自ThreadPoolExecutor。它主要用来在给定的延迟之后运行任务,或者定期执行任务。

public class ScheduledThreadPoolExecutorTest {
    public static void main(String[] args) {
        ScheduledThreadPoolExecutor threadPoolExecutor = new ScheduledThreadPoolExecutor(2);
        threadPoolExecutor.scheduleAtFixedRate(() -> System.out.println(1), 2, 3, TimeUnit.SECONDS);
    }
}

image-20230905231249081

上述代码会延迟2秒每隔3秒在控制台打印1。

原理解析

1.延迟队列介绍

看源码之前,先看看延迟队列,延迟任务。

DelayQueue是一个支持延时获取元素的无界阻塞队列队列使用PriorityQueue来实现队列中的元素必须实现Delayed接口,在创建元素时可以指定多久才能从队列中获取当前元素。只有在延迟期满时才能从队列中提取元素。

实现Delayed接口,延迟任务

class MyDelayedTask implements Delayed {
    // 当前任务创建时间
    private long start = System.currentTimeMillis();
    // 延时时间
    private long time;

    // 初始化
    public MyDelayedTask(long time) {
        this.time = time;
    }

    /**
     * 需要实现的接口,获得延迟时间(用过期时间-当前时间)
     */
    @Override
    public long getDelay(TimeUnit unit) {
        return unit.convert((start + time) - System.currentTimeMillis(), TimeUnit.MILLISECONDS);
    }

    /**
     * 用于延迟队列内部比较排序(当前时间的延迟时间 - 比较对象的延迟时间)
     */
    @Override
    public int compareTo(Delayed o) {
        return (int) (this.getDelay(TimeUnit.MILLISECONDS) - o.getDelay(TimeUnit.MILLISECONDS));
    }
}

任务生产者向延迟队列中添加任务,消费线程向队列中拉取任务,(比较延迟时间)消费任务。

2.线程池核心原理

image-20230905232648950

  • 当前的线程池个数小于核心线程数,直接添加核心线程即可
  • 当前的线程池个数大于等于核心线程数,将任务添加至阻塞队列中
  • 如果添加阻塞队列失败(队列满了),则需要添加非核心线程数处理任务
  • 如果添加非核心线程数失败(线程池满了),执行拒绝策略

3.定时执行原理

3.1构造器

    public ScheduledThreadPoolExecutor(int corePoolSize) {
        super(corePoolSize, Integer.MAX_VALUE, 0, NANOSECONDS,
              new DelayedWorkQueue());
    }

    public ThreadPoolExecutor(int corePoolSize,
                              int maximumPoolSize,
                              long keepAliveTime,
                              TimeUnit unit,
                              BlockingQueue<Runnable> workQueue) {
        this(corePoolSize, maximumPoolSize, keepAliveTime, unit, workQueue,
             Executors.defaultThreadFactory(), defaultHandler);
    }

阻塞队列DelayedWorkQueue,有界队列

    static class DelayedWorkQueue extends AbstractQueue<Runnable>
        implements BlockingQueue<Runnable> {
       private static final int INITIAL_CAPACITY = 16;
        private RunnableScheduledFuture<?>[] queue =
            new RunnableScheduledFuture<?>[INITIAL_CAPACITY];
        
        // ...
    }

image-20230905232924573

ScheduledThreadPoolExecutor内部自定义阻塞队列

3.2周期执行

    public ScheduledFuture<?> scheduleAtFixedRate(Runnable command,
                                                  long initialDelay,
                                                  long period,
                                                  TimeUnit unit) {
        if (command == null || unit == null)
            throw new NullPointerException();
        if (period <= 0)
            throw new IllegalArgumentException();
        // 初始化任务
        ScheduledFutureTask<Void> sft =
            new ScheduledFutureTask<Void>(command,
                                          null,
                                          triggerTime(initialDelay, unit),
                                          unit.toNanos(period));
        RunnableScheduledFuture<Void> t = decorateTask(command, sft);
        sft.outerTask = t;
        // 添加任务,创建线程处理
        delayedExecute(t);
        return t;
    }

    private void delayedExecute(RunnableScheduledFuture<?> task) {
        // 将当前任务添加至延时队列
        super.getQueue().add(task);
        // 创建核心线程并启动 
        ensurePrestart();
    }

	// 触发时间延时实现
    private long triggerTime(long delay, TimeUnit unit) {
        return now() + delay;
    }

队列任务RunnableScheduledFuture

image-20230905233147213

ScheduledThreadPoolExecutor内部自定义延时任务

定时线程池通过延时队列来达到定时的目的

仅仅使用延迟队列是无法到达定时执行的目的,因此看从阻塞队列获取任务以及启动线程执行任务代码。

  • 获取任务
for (;;) {
    // 从延时队列中获取任务
    Runnable r = workQueue.take();
}
public RunnableScheduledFuture<?> take(){
    // 死循环获取任务
    for (;;) {
        // 获取队列第一个任务
        RunnableScheduledFuture<?> first = queue[0];
        
        // 如果当前队列任务为空,则等待
        if (first == null){
            available.await();
        }
                        
        // 获取当前任务的时间
        long delay = first.getDelay(NANOSECONDS);
        
        // 到延迟时间
        if (delay <= 0){
            // 弹出当前任务【数组头任务】
            return finishPoll(first);
        }
                   
    }
}
// 延迟时间减去当前时间
public long getDelay(TimeUnit unit) {
    return unit.convert(time - now(), NANOSECONDS);
}
  • 执行任务

当拿到任务(ScheduledFutureTask)之后,会执行任务:task.run()【ThreadPoolExecutor#runWorker】

public void run() {
   // 执行当前的任务
   if (ScheduledFutureTask.super.runAndReset()) {
        setNextRunTime();
        reExecutePeriodic(outerTask);
    }
}

protected boolean runAndReset() {
    if (state != NEW){
        return false;
    }
    int s = state;
    try {
        Callable<V> c = callable;
        if (c != null && s == NEW) {
            try {
                // 执行任务
                c.call(); 
                // 【重点】如果任务正常执行成功的话,这里会将ran置为true
                // 如果任务执行期间发生异常,会被下面直接捕捉到,不会将此处的ran置为true
                ran = true;
            } catch (Throwable ex) {
                // 出现异常会将state置为EXCEPTIONAL
                // 标记当前任务执行失败并将异常赋值到结果
                setException(ex);
            }finally {
                 s = state;
            }
        }
    }
    // ran:当前任务是否执行成功
    // s:当前任务状态
    // ran为false:当前任务执行失败
    // s == NEW = false:当前任务状态出现异常
    return ran && s == NEW;
}

如果定时任务执行发送异常, runAndReset 返回 false,无法继续执行:

if (ScheduledFutureTask.super.runAndReset()) {
    // 重置下一次任务执行时间
    setNextRunTime();
    // 将任务重新丢进队列
    reExecutePeriodic(outerTask);
}

小结

1.执行任务发生异常会影响定时线程池?

不会。任务执行异常不会影响线程池,只是线程池将当前任务给丢失,没有继续放到队列中(该任务后续就不会执行),线程池仍一直处于运行状态。

2.定时线程池实现原理

线程池 + 延迟队列

3.使用注意事项

使用定时线程池,定时任务一定要加Try Catch

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/978159.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

QT DAY7

主要完成多人聊天室&#xff0c;注册与登录使用sql3数据库进行对密码的保存&#xff0c;避免了用户重复登录、错误密码登录、重复注册的问题&#xff0c;之后使用TCP通信&#xff0c;连接上服务器后可在聊天室多人交流

怎么处理zk或redis脑裂

很极端场景会出现脑裂 什么是分布式的脑裂 怎么理解zk脑裂 就是ZK&#xff0c;与客户端可能因为网络原因&#xff0c;客户端A还在跑着后续程序&#xff0c;而zk与客户端之前的心跳断了&#xff0c;此zk就把这节点给删除了&#xff0c;这时另一个客户会加锁成功&#xff0c;就样…

[Java]异常

目录 1.异常的概念与体系结构 1.1异常的概念 1.1.1算术异常 1.1.2数组越界异常 1.1.3空指针异常 1.2异常的体系结构 1.3异常的分类 2.异常的处理 2.1 防御式编程 2.2异常的抛出 2.3异常的捕获 2.3.1 异常声明throws 将光标放在抛出异常方法上&#xff0c;alt Insert …

咖啡店小程序:吸引顾客的创新营销手段

近日&#xff0c;“酱香拿铁”的大火让大家再次把目标聚焦在年轻人都喜欢的咖啡上。现在咖啡已经成为年轻一代的社交硬通货&#xff0c;咖啡店也遍地开花。而随着移动互联网的快速发展&#xff0c;咖啡店小程序已经成为了各大咖啡店主的选择&#xff0c;因为它提供了便捷的方式…

【Leetcode-面试经典150题-day22】

目录 97. 交错字符串 97. 交错字符串 题意&#xff1a; 给定三个字符串 s1、s2、s3&#xff0c;请你帮忙验证 s3 是否是由 s1 和 s2 交错 组成的。 两个字符串 s 和 t 交错 的定义与过程如下&#xff0c;其中每个字符串都会被分割成若干 非空 子字符串&#xff1a; s s1 s2 …

便捷、快速、稳定、高性能!以 GPU 实例演示 Alibaba Cloud Linux 3 对 AI 生态的支持 | 龙蜥技术

编者按&#xff1a;日前&#xff0c;Alibaba Cloud Linux 3 为使 AI 开发体验更高效&#xff0c;提供了一些优化升级&#xff0c;本文为“Alibaba Cloud Linux 3 AI 能力介绍”系列文章预告篇&#xff0c;以 GPU 实例为例&#xff0c;为大家演示 Alibaba Cloud Linux 3 对 AI 生…

Vue + Element UI 前端篇(五):国际化实现

Vue Element UI 实现权限管理系统 前端篇&#xff08;五&#xff09;&#xff1a;国际化实现 国际化支持 1.安装依赖 执行以下命令&#xff0c;安装 i18n 依赖。 yarn add vue-i18n $ yarn add vue-i18n yarn add v1.9.4 warning package-lock.json found. Your project …

数据分析必知的统计知识——区间估计(其四)

4. 区间估计 还以为你被上节课的内容唬住了~终于等到你&#xff0c;还好没放弃&#xff01; 本节我们将说明两个问题&#xff1a;总体均值 μ \mu μ 的区间估计和总体比例 p ˉ \bar{p} pˉ​ 的区间估计。 区间估计经常用于质量控制领域来检测生产过程是否正常运行或者在…

机车整备场数字孪生 | 图扑智慧铁路

机车整备场是铁路运输系统中的重要组成部分&#xff0c;它承担着机车的维修、保养和整备工作&#xff0c;对保障铁路运输的运维和安全起着至关重要的作用。 随着铁路运输的发展、机车技术的不断进步&#xff0c;以及数字化转型的不断推进&#xff0c;数字孪生技术在机车整备场…

【Nginx23】Nginx学习:响应头与Map变量操作

Nginx学习&#xff1a;响应头与Map变量操作 响应头是非常重要的内容&#xff0c;浏览器或者客户端有很多东西可能都是根据响应头来进行判断操作的&#xff0c;比如说最典型的 Content-Type &#xff0c;之前我们也演示过&#xff0c;直接设置一个空的 types 然后指定默认的数据…

OPC UA Tunnel提高了OPC Classic通信的安全性

2023年8月22日&#xff0c;Softing工业自动化推出了dataFEED OPC Suite 5.30版本。该版本增加了两项新功能&#xff1a;OPC UA Tunnel和InfluxDB数据库连接。 &#xff08;OPC UA Tunnel提高了OPC Classic通信的安全性&#xff09; | OPC UA Tunnel——用于提高OPC Classic通信…

日志框架Slf4j作用及其实现原理

目录 1 设计模式门面模式2 slf4j源码解析 1 设计模式门面模式 设计模式之门面模式与装饰器模式详解和应用&#xff1a;https://blog.csdn.net/ZGL_cyy/article/details/129073521 slf4j是门面模式的典型应用&#xff0c;因此在讲slf4j前&#xff0c;我们先简单回顾一下门面模…

前端日期比较大小(超简单版,不需要转换时间戳)

思路&#xff08;把日期转换为Number类型进行比较&#xff09; 效果如图&#xff1a; 第一步&#xff1a;如果获取到的日期是 &#xff1a;"2023-08-03 00:00:00" 用 timesss.split( )[0] // .split( ) 中间有个空格哦 timesss是自己数据的变…

nlp系列(7)实体识别(Bert)pytorch

模型介绍 本项目是使用Bert模型来进行文本的实体识别。 Bert模型介绍可以查看这篇文章&#xff1a;nlp系列&#xff08;2&#xff09;文本分类&#xff08;Bert&#xff09;pytorch_bert文本分类_牧子川的博客-CSDN博客 模型结构 Bert模型的模型结构&#xff1a; 数据介绍 …

解决Android U无法通过adb安装应用(Caller has no access to session -1)的问题

在Android U&#xff08;14&#xff09;上&#xff0c;对通过adb安装应用做了限制。默认的情况下&#xff0c;当执行以下命令的时候 adb install XXX.apk会提示执行异常 Performing Streamed Install adb: failed to install XXX.apk: Exception occurred while executing in…

java面试之ThreadLocal问题

什么是ThreadLocal,它的基本用法是什么 简单来说就是能在多线程中保持变量独立的线程对象 不用Threadlocal多线程访问同一个变量会出现的问题 package com.pxx;/*** Created by Administrator on 2023/9/3.*/ public class Demo1 {private String v1;public String getV1() …

群晖 DS918通过CISCO SG250 LACP 链路聚合效果不佳的问题解决

问题表现 使用的是CISCO交换机打开LACP 链路聚合&#xff0c;且DS918上完成接口聚合并配置为平衡TCP模式后。通过IPREF测速整体网络性能仅能达到300Mbps左右。 问题解决 检查CISCO交换机LAG配置中&#xff0c;针对DS918的接口组是否正确配置了流量配置。请按照如下图所示&#…

易云维®医院后勤管理系统软件利用物联网智能网关帮助实现医院设备实现智能化、信息化管理

近年来&#xff0c;我国医院逐渐意识到医院设备信息化管理的重要性&#xff0c;逐步建立医院后勤管理系统软件&#xff0c;以提高信息化管理水平。该系统是利用数据库技术&#xff0c;为医院的中央空调、洁净空调、电梯、锅炉、医疗设备等建立电子档案&#xff0c;把设备监控、…

Python中的PYTHONPATH

迷途小书童 读完需要 4分钟 速读仅需 2 分钟 大家好&#xff0c;我是迷途小书童&#xff01; 今天来聊聊 PYTHONPATH。 PYTHONPATH 是一个环境变量&#xff0c;它是一个列表&#xff0c;列表的元素是目录&#xff0c;也就是一些文件夹的路径&#xff0c;它告诉 Python 解释器去…

《自然的艺术形态》

艺术是科学的最高形式。《自然的艺术形态》是恩斯特海克尔在19世纪博物学和生物学的最高峰对自然界所作出的最美阐释。透过自然科学巨匠的慧眼&#xff0c;人类能多一个视角&#xff0c;认识栩栩如生的自然万物&#xff0c;其奇美&#xff0c;其壮观&#xff0c;若非建立在自然…