百度360必应搜狗淘宝本站头条
当前位置:网站首页 > 编程网 > 正文

Java定时任务之ScheduledThreadPoolExecutor

yuyutoo 2024-10-12 00:06 9 浏览 0 评论

ScheduledThreadPoolExecutor是JDK5提供的可执行定时任务的一个工具类,可以在多线程环境下延迟执行任务或者定期执行任务;和Timer类似,它也提供了三种定时模式:

  1. 延迟执行任务
  2. 固定延迟的定期执行(fixed delay)
  3. 按照固定的周期执行(fixed rate)

延迟执行任务

任务将按照给定的时间延迟delay后开始执行;对应的方法如下:

schedule(Runnable command, long delay, TimeUnit unit)  
schedule(Callable<V> callable, long delay, TimeUnit unit)  

下面我们通过一个例子检验下结果是否正确:

ScheduledExecutorService executor = Executors.newSingleThreadScheduledExecutor();
        PrintUtil.print("start schedule a task.");
        executor.schedule(new Runnable() {
            @Override
            public void run() {
                PrintUtil.print("task is running.");
            }
}, 5, TimeUnit.SECONDS);

我们计划了一个5秒钟后执行的任务,通过打印结果可以看到确实按照给定时间执行了:

15:44:07--start schedule a task.
15:44:12--task is running.

固定延迟的定期执行

任务第一次按照给定的初始延迟initialDelay执行,后续每一次执行的时间为上一次任务的结束时间加上给定的period后执行;对应的方法如下:

scheduleAtFixedRate(Runnable command, long initialDelay, long period, TimeUnit unit)

同样我们通过一个例子检验下结果是否正确:

ScheduledExecutorService executor = Executors.newSingleThreadScheduledExecutor();
        PrintUtil.print("start schedule a task.");
        executor.scheduleWithFixedDelay(new Runnable() {
            @Override
            public void run() {
                PrintUtil.print("task is running.");
                try {
                    Thread.sleep(2000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
}, 0, 5, TimeUnit.SECONDS);

我们计划了一个定期(每5秒钟)延迟执行的任务,第一次任务立即执行,每次任务执行时长2秒钟,通过打印的日志我们可以看到每次任务开始执行的时间为:上次任务结束时间+5秒钟:

15:55:16--start schedule a task.
15:55:16--task is running.
15:55:18--task is finished.
15:55:23--task is running.
15:55:25--task is finished.
15:55:30--task is running.
15:55:32--task is finished.

按照固定的周期执行

任务第一次按照给定的初始延迟initialDelay执行,后续每一次执行的时间为固定的时间间隔period,如果线程池中工作线程不够则任务顺延执行;对应的方法如下:

scheduleWithFixedDelay(Runnable command, long initialDelay, long period, TimeUnit unit)

同样我们通过一个例子检验下结果是否正确:

ScheduledExecutorService executor = Executors.newScheduledThreadPool(10);
        PrintUtil.print("start schedule a task.");
        executor.scheduleAtFixedRate(new Runnable() {
            @Override
            public void run() {
                PrintUtil.print("task is running.");
                try {
                    Thread.sleep(2000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                PrintUtil.print("task is finished.");
            }
}, 0, 5, TimeUnit.SECONDS);

我们创建了一个核心线程池为10的ScheduledThreadPoolExecutor,并计划了一个定期(每5秒钟)执行一次的任务,过打印的日志我们可以看到每次任务开始执行的时间为:上次任务开始时间+5秒钟:

16:02:43--start schedule a task.
16:02:43--task is running.
16:02:45--task is finished.
16:02:48--task is running.
16:02:50--task is finished.


上面的例子都是计划了一个任务,如果是有多个定时任务同时执行会怎么样呢?

如果线程足够并且CPU资源足够,那就会同时执行,如果线程或者CPU资源不够那只能排队执行了。有兴趣的话可以克隆文末的测试代码,里面提供了一些测试的例子。

底层原理

ScheduledThreadPoolExecutor是如何保证我们计划的任务都是按照正确的时间点执行的呢?

其内部实现了一个阻塞队列DelayedWorkQueue,所有的任务都会放到这个队列里。这个阻塞队列内部通过一个数组来保存这些任务,并且基于最小堆排序,按照每个任务的下次执行时间进行排序,这样就保证了执行线程拿到的这个队列中的第一个元素就是最接近当前时间执行的任务了。

相关的源码如下:

// 保存定时任务的队列
private RunnableScheduledFuture<?>[] queue = new RunnableScheduledFuture<?>[INITIAL_CAPACITY];
// 最小堆排序相关的方法
private void siftUp(int k, RunnableScheduledFuture<?> key)
private void siftDown(int k, RunnableScheduledFuture<?> key)

那时间上是如何保证的呢?

DelayedWorkQueue重写了take和poll方法,利用了AQS的ConditionObject机制使当前线程休眠,等时间到了再唤醒线程去拿第一个任务。

关于AQS和ConditonObject的介绍,可以参考下文末的链接。

public RunnableScheduledFuture<?> take() throws InterruptedException {
            final ReentrantLock lock = this.lock;
            lock.lockInterruptibly();
            try {
                for (;;) {
                    RunnableScheduledFuture<?> first = queue[0];
                    if (first == null)
                        available.await();
                    else {
                        long delay = first.getDelay(NANOSECONDS);
                        if (delay <= 0)
                            return finishPoll(first);
                        first = null; // don't retain ref while waiting
                        if (leader != null)
                            available.await();
                        else {
                            Thread thisThread = Thread.currentThread();
                            leader = thisThread;
                            try {
                                available.awaitNanos(delay);
                            } finally {
                                if (leader == thisThread)
                                    leader = null;
                            }
                        }
                    }
                }
            } finally {
                if (leader == null && queue[0] != null)
                    available.signal();
                lock.unlock();
            }
}

优点

作为对JDK1.3推出的Timer的替代,ScheduledThreadPoolExecutor有如下优点:

  1. 它支持多个定时任务同时执行,而Timer是单线程执行的
  2. 它通过System.nanoTime()保证了任务执行时间不受操作系统时间变化的影响
  3. 一个定时任务抛出异常,其他定时任务不受影响,而Timer却不支持这一点

Demo代码

src/main/java/net/weichitech/util/ScheduledThreadPoolExecutorDemo.java · 小西学编程/java-learning - Gitee.com

参考文章

Java定时任务之Timer原理解析

JAVA并发之ReentrantLock原理解析

相关推荐

国产RISC-V终端Sipeed Lichee Console4A上架,1699元起

IT之家12月11日消息,国内著名开源硬件厂商Sipeed矽速科技推出RISC-V终端LicheeConsole4A,售价1699元(不带LM4A模块)-3299元。Li...

H3C交换机常用配置命令

1、配置主机名...

ThinkPad老版Bios中英文对应详解

ThinkPad老版Bios中文对应详解。解决方案:...

澳大利亚Console Connect与非洲数据中心达成战略合作,增强整个非洲的数据连接

据techafricanews网11月13日报道,在2024年非洲科技节上,澳大利亚ConsoleConnect与非洲数据中心共同宣布了一项具有里程碑意义的战略合作协议,该协议致力于提升非洲大陆关键...

多功能调焦 腾龙TAP-in Console延期发售

最新消息传出,腾龙TAP-inConsole(modeltap-01)多功能调焦器由于生产方面原因原本预计3月24日发售延迟至3月30日,腾龙的这款调焦器功能类似于适马的USBDock调焦底座,...

关于交换机上存在的不同接口介绍(一)

生活中常见的电子设备有很多,其中明交换机主要起到的是连接的作用,上面有非常多的接口,下面就一起来看看这些不同的接口作用是什么。1、RJ-45接口这是我们见的最多、应用最广的一种接口类型,它属于双绞线以...

颜值更高?微软新推精英版Xbox One手柄

本月,微软的XboxOne升级了更强大的版本——XboxOneEliteconsole(精英版?),升级版的控制手柄功能更强大,可以主导你的客厅;从曝光的图片上看得出,新版XboxOneE...

“全球首个”:Console Connect为全球物联网项目推出连接解决方案

据DevelopingTelecoms2月27日报道,在MWC2023上,全球网络即服务(NaaS)平台ConsoleConnect推出了“全球首个”私有连接解决方案,可帮助企业在全球范围内动态...

密码遗忘专题——Console口密码遗忘

如果忘记了Console口密码,用户可以通过以下两种方式来设置新的Console口密码:方法一:通过STelnet/Telnet登录设备修改Console口密码。...

为锐捷路由器交换机开启web和telnet,实现轻松管理

笔者上一篇文章写了关于锐捷二层交换机配置教程,那么接下来讲一下锐捷的路由交换设备配置web、telnet技巧。同样,今天的教程也是基于命令行,比较简单,适合新手小白进行学习。准备工作配置前准备:con...

C# - 类文件构成,C#基本语法,Console属性与方法 007

类文件(.cs)构成类文件主要分为引用命名空间与自己项目的命名空间...

Console OS系统帮你在PC上自由切换Windows和安卓应用

通过ConsoleOS你可以在你的PC上安装自己喜欢的安卓游戏,需要时之间切换回Windows进行其他工作,ConsoleOS能够完全利用你的PC硬件配置,该系统可以安装在PC的内置硬盘里,也可以...

交换机通过串口线console口登录设备

一、功能简介:PC端通过设备的Console口登录,实现对第一次上电的设备进行基本配置和管理。...

(每日持续更新)jdk api之Console基础、应用、实战

博主18年的互联网软件开发经验,从一名程序员小白逐步成为了一名架构师,我想通过平台将经验分享给大家,因此博主每天会在各个大牛网站点赞量超高的博客等寻找该技术栈的资料结合自己的经验,晚上进行用心精简、整...

KFC推出“真正的次世代主机”KFConsole!真4K/120帧

今天KFCGaming官方推特发布了一则“主机”宣传片,正式公布了旗下名为KFConsole的全新主机产品。并宣称:游戏的未来就在这里,介绍真正的次世代主机!根据官方介绍,本款KFC主机功能强大,支...

取消回复欢迎 发表评论: