Java开发笔记(一百零五)几种定时器线程池
前面介绍了普通线程池的用法,就大多数任务而言,它们对具体的执行时机并无特殊要求,最多是希望早点跑完早点出结果。不过对于需要定时执行的任务来说,它们要求在特定的时间点运行,并且往往不止运行一次,还要周期性地反复运行。由于普通线程池满足不了此类定时运行的需求,因此Java又提供了定时器线程池来实现定时与周期执行任务的功能。
普通线程池的工具类名叫ExecutorService,定时器线程池的工具类则叫做ScheduledExecutorService,添加了Scheduled前缀,表示它是一种有计划的、预先安排好的线程池。有别于划分了四大类的普通线程池,定时器线程池仅仅分成了两类:单线程的定时器线程池和固定数量的定时器线程池。其中单线程的定时器线程池通过newSingleThreadScheduledExecutor方法获得,它的创建代码示例如下:
// 创建一个延迟一次的单线程定时器
ScheduledExecutorService pool = (ScheduledExecutorService) Executors.newSingleThreadScheduledExecutor();
至于固定数量的定时器线程池则通过newScheduledThreadPool方法获得,它的创建代码示例如下:
// 创建一个延迟一次的多线程定时器(线程池大小为3)
ScheduledExecutorService pool = (ScheduledExecutorService) Executors.newScheduledThreadPool(3);
虽然定时器线程池只有两类,但定时器的调度方式有三种之多,主要是依据启动次数与周期长度来划分,详细说明如下:
1、定时任务只启动一次。
此时调用线程池对象的schedule方法,该方法的第一个参数为任务实例,第二个和第三个参数分别是延迟执行的时长及其单位。
2、每间隔若干时间周期启动定时任务。
此时调用线程池对象的scheduleAtFixedRate方法,该方法的第一个参数为任务实例,第二个参数为首次执行的延迟时长,第三个参数分别为后续运行的间隔时长,第四个参数则为时长单位。
3、固定延迟若干时间启动定时任务。
此时调用线程池对象的scheduleWithFixedDelay方法,该方法的参数说明基本同scheduleAtFixedRate方法。两个方法的区别在于:前者的间隔时间从上个任务的开始时间起计算,后者的间隔时间从上个任务的结束时间起计算。
除了以上的三个调度方法,ScheduledExecutorService还拥有ExecutorService的全部方法,包括getPoolSize、getActiveCount、shutdown等等,因为它本来就是从ExecutorService派生而来的呀。
下面做个实验观察一下两种定时器线程池的运行过程,实验开始前先定义一个参观任务,主要用来打印当前的操作日志,包括操作时间、操作线程、操作描述等信息。参观任务的代码例子如下所示:
// 定义一个参观任务
private static class Visit implements Runnable {
private String name; // 任务名称
private int index; // 任务序号
public Visit(String name, int index) {
this.name = name;
this.index = index;
} @Override
public void run() {
// 以下打印操作日志,包括操作时间、操作线程、操作描述等信息
String desc = String.format("%s的第%d个任务到此一游", name, index);
PrintUtils.print(Thread.currentThread().getName(), desc);
}
};
然后命令单线程的定时器线程池调用schedule方法执行一次的定时任务,具体的实验代码示例如下:
// 测试延迟一次的单线程定时器
private static void testSingleScheduleOnce() {
// 创建一个延迟一次的单线程定时器
ScheduledExecutorService pool = (ScheduledExecutorService) Executors.newSingleThreadScheduledExecutor();
for (int i=0; i<5; i++) { // 循环开展5个调度
// 创建一个参观任务
Visit visit = new Visit("延迟一次的单线程定时器", i);
// 命令线程池开展任务调度。延迟1秒后执行参观任务
pool.schedule(visit, 1, TimeUnit.SECONDS);
}
}
运行以上的实验代码,观察到如下的线程池日志:
15:49:16.122 pool-1-thread-1 延迟一次的单线程定时器的第0个任务到此一游
15:49:16.123 pool-1-thread-1 延迟一次的单线程定时器的第1个任务到此一游
15:49:16.123 pool-1-thread-1 延迟一次的单线程定时器的第2个任务到此一游
15:49:16.124 pool-1-thread-1 延迟一次的单线程定时器的第3个任务到此一游
15:49:16.124 pool-1-thread-1 延迟一次的单线程定时器的第4个任务到此一游
由日志可见,该定时器线程池自始至终只有唯一一个的线程在运行。
再来测试固定数量的定时器线程池,此时换成调用scheduleAtFixedRate方法,准备以固定频率周期性地执行定时任务,具体的实验代码示例如下:
// 测试固定速率的多线程定时器
private static void testMultiScheduleRate() {
// 创建一个固定速率的多线程定时器(线程池大小为3)
ScheduledExecutorService pool = (ScheduledExecutorService) Executors.newScheduledThreadPool(3);
for (int i=0; i<5; i++) { // 循环开展5个调度
// 创建一个参观任务
Visit visit = new Visit("固定速率的多线程定时器", i);
// 命令线程池开展任务调度。第一次延迟1秒后执行参观任务,以后每间隔3秒执行下一个参观任务
pool.scheduleAtFixedRate(visit, 1, 3, TimeUnit.SECONDS);
}
}
运行以上的实验代码,观察到如下的线程池日志:
15:50:21.859 pool-1-thread-1 固定速率的多线程定时器的第0个任务到此一游
15:50:21.859 pool-1-thread-2 固定速率的多线程定时器的第1个任务到此一游
15:50:21.859 pool-1-thread-3 固定速率的多线程定时器的第2个任务到此一游
15:50:21.860 pool-1-thread-3 固定速率的多线程定时器的第3个任务到此一游
15:50:21.861 pool-1-thread-3 固定速率的多线程定时器的第4个任务到此一游
15:50:24.790 pool-1-thread-3 固定速率的多线程定时器的第1个任务到此一游
15:50:24.791 pool-1-thread-3 固定速率的多线程定时器的第3个任务到此一游
15:50:24.792 pool-1-thread-3 固定速率的多线程定时器的第4个任务到此一游
15:50:24.793 pool-1-thread-2 固定速率的多线程定时器的第2个任务到此一游
15:50:24.798 pool-1-thread-1 固定速率的多线程定时器的第0个任务到此一游
由日志可见,该定时器线程池一共开启了三个线程来执行定时任务,注意到每个任务的前后日志间隔时间不足3秒,正好说明间隔的3秒并非前后两次运行的首尾间隔。
那么调用方法改成scheduleWithFixedDelay,试试以固定间隔周期性地执行定时任务会是什么样的,具体的实验代码示例如下:
// 测试固定延迟的多线程定时器
private static void testMultiScheduleDelay() {
// 创建一个固定速率的多线程定时器(线程池大小为3)
ScheduledExecutorService pool = (ScheduledExecutorService) Executors.newScheduledThreadPool(3);
for (int i=0; i<5; i++) { // 循环开展5个调度
// 创建一个参观任务
Visit visit = new Visit("固定延迟的多线程定时器", i);
// 命令线程池开展任务调度。第一次延迟1秒后执行参观任务,以后每3秒执行下一个参观任务
pool.scheduleWithFixedDelay(visit, 1, 3, TimeUnit.SECONDS);
}
}
运行以上的实验代码,观察到如下的线程池日志:
16:10:19.281 pool-1-thread-1 固定延迟的多线程定时器的第0个任务到此一游
16:10:19.281 pool-1-thread-2 固定延迟的多线程定时器的第1个任务到此一游
16:10:19.281 pool-1-thread-3 固定延迟的多线程定时器的第2个任务到此一游
16:10:19.283 pool-1-thread-3 固定延迟的多线程定时器的第3个任务到此一游
16:10:19.283 pool-1-thread-2 固定延迟的多线程定时器的第4个任务到此一游
16:10:22.283 pool-1-thread-1 固定延迟的多线程定时器的第1个任务到此一游
16:10:22.284 pool-1-thread-2 固定延迟的多线程定时器的第3个任务到此一游
16:10:22.285 pool-1-thread-3 固定延迟的多线程定时器的第2个任务到此一游
16:10:22.286 pool-1-thread-3 固定延迟的多线程定时器的第4个任务到此一游
16:10:22.287 pool-1-thread-1 固定延迟的多线程定时器的第0个任务到此一游
由日志可见,此时每个任务的前后日志时间均不小于3秒,证明了scheduleWithFixedDelay方法的确采取了固定间隔而非固定速率。
更多Java技术文章参见《Java开发笔记(序)章节目录》
Java开发笔记(一百零五)几种定时器线程池的更多相关文章
- Java开发笔记(九十五)NIO配套的文件工具Files
NIO不但引进了高效的文件通道,而且新增了更加好用的文件工具家族,包括路径组工具Paths.路径工具Path.文件组工具Files.先看路径组工具Paths,该工具提供了静态方法get,输入某个文件的 ...
- Java开发笔记(十五)短路逻辑运算的优势
前面提到逻辑运算只能操作布尔变量,这其实是不严谨的,因为经过Java编程实现,会发现“&”.“|”.“^”这几个逻辑符号竟然可以对数字进行运算.譬如下面的代码就直接对数字分别开展了“与”.“或 ...
- Java开发笔记(序)章节目录
现将本博客的Java学习文章整理成以下笔记目录,方便查阅. 第一章 初识JavaJava开发笔记(一)第一个Java程序Java开发笔记(二)Java工程的帝国区划Java开发笔记(三)Java帝国的 ...
- Java开发笔记(一百零四)普通线程池的运用
前面介绍了线程的基本用法,以及多线程并发的问题处理,但实际开发中往往存在许多性质相似的任务,比如批量发送消息.批量下载文件.批量进行交易等等.这些同类任务的处理流程一致,不存在资源共享问题,相互之间也 ...
- Java开发笔记(一百零二)信号量的请求与释放
前面介绍了同步与加锁两种并发处理机制,虽然加锁比起同步要灵活一些,但是加锁在某些高级场合依然力有未逮,包括但不限于下列几点:1.某块代码被加锁之后,对其它线程而言就处于繁忙状态,缺乏弹性的阈值范围:2 ...
- Java开发笔记(一百零九)XML报文的定义和解析
前面介绍了JSON格式的报文解析,虽然json串短小精悍,也能有效表达层次结构,但是每个元素只能找到对应的元素值,不能体现更丰富的样式特征.比如某个元素除了要传输它的字符串文本,还想传输该文本的类型. ...
- Java开发笔记(五十)几种开放性修饰符
前面介绍子类继承父类的时候,提到了public(公共)和private(私有)两个修饰符,其中public表示它所修饰的实体是允许外部访问的:而private表示它所修饰的实体不允许外部访问,只能在当 ...
- Java开发笔记(一百零三)线程间的通信方式
前面介绍了多线程并发之时的资源抢占情况,以及利用同步.加锁.信号量等机制解决资源冲突问题,不过这些机制只适合同一资源的共享分配,并未涉及到某件事由的前因后果.日常生活中,经常存在两个前后关联的事务,像 ...
- Java开发笔记(一百零一)通过加解锁避免资源冲突
前面介绍了如何通过线程同步来避免多线程并发的资源冲突问题,然而添加synchronized的方式只在简单场合够用,在一些高级场合就暴露出它的局限性,包括但不限于下列几点:1.synchronized必 ...
随机推荐
- 学习Spring-Data-Jpa(十)---注解式方法查询之@Query、@Modifying与派生delete
1.@Query 对于少量的查询,使用@NamedQuery在实体上声明查询是一种有效的办法,并且可以很好的工作.由于查询本身绑定到执行它们的java方法,实际上可以通过Spring-Data-Jpa ...
- shell脚本 基础应用
变量分为普通变量可只读变量 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 ...
- circus 进程以及socket 管理工具&&docker运行
circus 是由mozilla 团队开发基于python 以及zeromq 的进程以及socket 管理的工具,类似supervisord 但是比supervisord 更灵活方便 来自官方的使用比 ...
- 干货 | 10分钟教你用column generation求解vehicle routing problems
OUTLINE 前言 VRPTW description column generation Illustration code reference 00 前言 此前向大家介绍了列生成算法的详细过程, ...
- javascript之随机密码[必包含大写,小写,数字]
js取两个数字之间的随机数: parseInt(Math.random()*(上限-下限+1)+下限) 如:取1-10之间的随机数 parseInt(Math.random()*(10-1+1)+ ...
- BMP图像信息隐藏
图像隐写算法LSB—Least Significant Bits,又称最不显著位.LSB算法就是将秘密信息嵌入到载体图像像素值得最低有效位,改变这一位置对载体图像的品质影响最小. 原理如下: 以实验用 ...
- kvm错误:failed to initialize KVM: Permission denied
错误1: 启动kvm容器报错: # virsh start hadoop-test error: Failed to start domain hadoop-testerror: internal e ...
- 【洛谷】P5024 保卫王国 (倍增)
前言 传送门 很多人写了题解了,我就懒得写了,推荐一篇博客 那就分享一下我的理解吧(说得好像有人看一样 对于每个点都只有选与不选两种情况,所以直接用倍增预处理出来两种情况的子树之内,子树之外的最值,最 ...
- 关于SpringCloud、SpringBoot简单讲解
什么是Spring Boot 用我的话来理解,Spring Boot就是整合了框架的框架,它让一切依赖都变得有序简单,你不用操心A.jar是什么版本,又依赖哪些版本的jar,它默认配置了很多框架的使 ...
- 经典批处理实现自动关机(BAT)
经典批处理实现自动关机1.BAT @ECHO offTITLE 自动关机程序 作者:廖晓青 :startCLSCOLOR 1frem 使用COLOR命令对控制台输出颜色进行更改MODE con: CO ...