ScheduledThreadPoolExecutor概述

我们在上一篇学习了ThreadPoolExecutor的实现原理:Java并发包源码学习系列:线程池ThreadPoolExecutor源码解析

本篇我们来学习一下在它基础之上的扩展:ScheduledThreadPoolExecutor。它继承了ThreadPoolExecutor并实现了ScheduledExecutorService接口,是一个可以在指定一定延迟时间后或者定时进行任务调度执行的线程池。

public class TestScheduledThreadPool {

    private static final ScheduledExecutorService scheduler = Executors.newScheduledThreadPool(1);

    public static void main (String[] args) throws InterruptedException {
scheduler.scheduleAtFixedRate(new Runnable() {
@Override
public void run () {
System.out.println("command .. " + new Date());
}
}, 0, 1, TimeUnit.SECONDS);
}
}

简单看一个demo吧,这里使用Executors工具类创建ScheduledExecutorService,起始就是实例化了一个ScheduledThreadPoolExecutor,当然我们自定义也是可以的。

接着调用scheduleAtFixedRate方法,指定延迟为0,表示立即执行, 指定period为1,以1s为周期定时执行该任务。

从整体感知ScheduledThreadPoolExecutor的执行

  1. 当调用scheduleAtFixedRate时,将会向延迟队列中添加一个任务ScheduledFutureTask。
  2. 线程池中的线程从延迟队列中获取任务,并执行。

类图结构

  • 可以通过Executors工具类创建,也可以通过构造方法创建。
    //Executors.java
public static ScheduledExecutorService newScheduledThreadPool(int corePoolSize) {
return new ScheduledThreadPoolExecutor(corePoolSize);
}
//ScheduledThreadPoolExecutor.java
public ScheduledThreadPoolExecutor(int corePoolSize) {
super(corePoolSize, Integer.MAX_VALUE, 0, NANOSECONDS,
new DelayedWorkQueue());
}
  • ScheduledThreadPoolExecutor继承了ThreadPoolExecutor并实现了ScheduledExecutorService接口。
  • 线程池队列使用DelayedWorkQueue,和DelayedQueue类似,是延迟队列。
  • ScheduledFutureTask是一个具有返回值的任务,继承自FutureTask。

ScheduledExecutorService

ScheduledExecutorService代表可在指定延迟后或周期性地执行线程任务线程池,提供了如下4个方法:

public interface ScheduledExecutorService extends ExecutorService {

	// 指定command任务将在delay延迟后执行
public ScheduledFuture<?> schedule(Runnable command,
long delay, TimeUnit unit);
// 指定callable任务将在delay延迟后执行
public <V> ScheduledFuture<V> schedule(Callable<V> callable,
long delay, TimeUnit unit);
// 指定command任务将在delay延迟后执行,而且以设定频率重复执行
// initialDelay + period 开始, initialDelay + n * period 处执行
public ScheduledFuture<?> scheduleAtFixedRate(Runnable command,
long initialDelay,
long period,
TimeUnit unit); // 创建并执行一个在给定初始延迟后首次启用的定期操作,随后在每一次执行终止和下一次执行开始之间
// 都存在给定的延迟。如果任务在任一一次执行时遇到异常,就会取消后续执行;
// 否则,只能通过程序来显式取消或终止该任务
public ScheduledFuture<?> scheduleWithFixedDelay(Runnable command,
long initialDelay,
long delay,
TimeUnit unit); }

ScheduledFutureTask

可以按照DelayQueue中的Delayed的元素理解,是具体放入延迟队列中的东西,可以看到实现了getDelay和compareTo方法。

  • getDelay获取元素剩余时间,也就是当前任务还剩多久过期,【剩余时间 = 到期时间 - 当前时间】。
  • compareTo方法作为排序规则,一般规定最快过期的元素放到队首,q.peek()出来的就是最先过期的元素。
    private class ScheduledFutureTask<V>
extends FutureTask<V> implements RunnableScheduledFuture<V> { /** FIFO队列中的序列号,time相同,序列号小的排在前面 */
private final long sequenceNumber; /** 任务将要被执行的时间,也就是过期时间 */
private long time; /**
* period == 0 当前任务是一次性的, 执行完毕后就退出
* period > 0 当前任务是fixed-delay任务,是固定延迟的定时可重复执行任务
* period < 0 当前任务是fixed-rate任务,是固定频率的定时可重复执行任务
*/
private final long period; /** The actual task to be re-enqueued by reExecutePeriodic */
RunnableScheduledFuture<V> outerTask = this; /**
* Index into delay queue, to support faster cancellation.
*/
int heapIndex; //... 省略构造函数 // 当前任务还剩多久过期
public long getDelay(TimeUnit unit) {
return unit.convert(time - now(), NANOSECONDS);
} // 队列中的比较策略
public int compareTo(Delayed other) {
if (other == this) // compare zero if same object
return 0;
if (other instanceof ScheduledFutureTask) {
ScheduledFutureTask<?> x = (ScheduledFutureTask<?>)other;
long diff = time - x.time;
if (diff < 0)
return -1;
else if (diff > 0)
return 1;
// time相同,序列号小的排在前面
else if (sequenceNumber < x.sequenceNumber)
return -1;
else
return 1;
}
long diff = getDelay(NANOSECONDS) - other.getDelay(NANOSECONDS);
return (diff < 0) ? -1 : (diff > 0) ? 1 : 0;
} //... 省略其他方法
}

FutureTask

FutureTask内部使用一个state变量表示任务状态。

public class FutureTask<V> implements RunnableFuture<V> {

    /**
*
* Possible state transitions:
* NEW -> COMPLETING -> NORMAL
* NEW -> COMPLETING -> EXCEPTIONAL
* NEW -> CANCELLED
* NEW -> INTERRUPTING -> INTERRUPTED
*/
private volatile int state;
private static final int NEW = 0; // 初始状态
private static final int COMPLETING = 1; // 执行中
private static final int NORMAL = 2; // 正常运行结束
private static final int EXCEPTIONAL = 3; // 运行中异常
private static final int CANCELLED = 4; // 任务被取消
private static final int INTERRUPTING = 5; // 任务正在被中断
private static final int INTERRUPTED = 6; // 任务已经被中断 }

schedule

提交一个延迟执行的任务,任务从提交时间算起延迟单位为unit的delay时间后开始执行。

如果提交的任务不是周期性的任务,任务只会执行一次。

    public ScheduledFuture<?> schedule(Runnable command,
long delay,
TimeUnit unit) {
// 参数校验
if (command == null || unit == null)
throw new NullPointerException();
// 任务转换: 把command任务转换为ScheduledFutureTask
RunnableScheduledFuture<?> t = decorateTask(command,
new ScheduledFutureTask<Void>(command, null,
triggerTime(delay, unit)));
// 添加任务到延迟队列
delayedExecute(t);
return t;
} // 将延迟时间转换为绝对时间,
private long triggerTime(long delay, TimeUnit unit) {
return triggerTime(unit.toNanos((delay < 0) ? 0 : delay));
}
// 将当前的那描述加上延迟的nanos后的long型值
long triggerTime(long delay) {
return now() +
((delay < (Long.MAX_VALUE >> 1)) ? delay : overflowFree(delay));
} private class ScheduledFutureTask<V>
extends FutureTask<V> implements RunnableScheduledFuture<V> { ScheduledFutureTask(Runnable r, V result, long ns) {
super(r, result); // 调用FutureTask的构造方法
this.time = ns;
this.period = 0; // 这里表示任务是一次性的
this.sequenceNumber = sequencer.getAndIncrement();
}
} // FutureTask.java
public class FutureTask<V> implements RunnableFuture<V> {
public FutureTask(Runnable runnable, V result) {
// 将runnable转化为callable
this.callable = Executors.callable(runnable, result);
// 设置当前的任务状态为NEW
this.state = NEW; // ensure visibility of callable
}
}

void delayedExecute(task)

  1. 首先判断当前线程池是否已经关闭,如果已经关闭则执行线程池的拒绝策略,否则将任务添加到延迟队列。
  2. 加入队列后,还要重新检查线程池是否被关闭,如果已经关闭则从延迟队列里删除刚才添加的任务,但此时可能线程池中的线程已经执行里面的任务,此时就需要取消该任务。
    private void delayedExecute(RunnableScheduledFuture<?> task) {
// 如果线程池关闭, 则执行拒绝策略
if (isShutdown())
reject(task);
else {
// 将任务添加到延迟队列
super.getQueue().add(task);
// 检查线程池状态,如果已经关闭,则从延迟队列里面删除刚才添加的任务
// 但此时可能线程池中的线程已经从任务队列里面移除了该任务
// 此时需要调用cancel 取消任务
if (isShutdown() &&
!canRunInCurrentRunState(task.isPeriodic()) &&
remove(task))
task.cancel(false);
else
// 确保至少一个线程在处理任务
ensurePrestart();
}
}

boolean canRunInCurrentRunState(periodic)

判断当前任务是否应该被取消。

    boolean canRunInCurrentRunState(boolean periodic) {
return isRunningOrShutdown(periodic ?
continueExistingPeriodicTasksAfterShutdown :
executeExistingDelayedTasksAfterShutdown);
}

periodic参数通过isPeriodic()得到,如果period为0,则为false。

相应的isRunningOrShutdown方法传入的参数就应该是executeExistingDelayedTasksAfterShutdown,默认为true,表示:其他线程调用了shutdown命令关闭线程池后,当前任务还是要执行

void ensurePrestart()

确保至少一个线程在处理任务:如果线程个数小于核心线程池数则新增一个线程,否则如果当前线程数为0,则新增一个线程。

    void ensurePrestart() {
int wc = workerCountOf(ctl.get());
// 增加核心线程数
if (wc < corePoolSize)
addWorker(null, true);
// 如果corePoolSize==0 也添加一个线程
else if (wc == 0)
addWorker(null, false);
}

ScheduledFutureTask#run()

具体执行任务的线程是Worker线程,任务执行是Worker线程调用任务的润方法执行,这里的任务是ScheduledFutureTask,也就是调用它的run方法。

        public void run() {
// 是否只执行一次 period != 0
boolean periodic = isPeriodic();
// 取消任务
if (!canRunInCurrentRunState(periodic))
cancel(false);
// 任务只执行一次, 调用FutureTask的run
else if (!periodic)
ScheduledFutureTask.super.run();
// 定时执行
else if (ScheduledFutureTask.super.runAndReset()) {
// 设置下一次运行时间
setNextRunTime();
// 重新加入延迟队列
reExecutePeriodic(outerTask);
}
}

FutureTask#run()

    public void run() {
// 如果任务不是NEW状态 直接返回
// 如果是NEW, 但是cas设置当前任务的持有者为当前线程失败 也直接返回
if (state != NEW ||
!UNSAFE.compareAndSwapObject(this, runnerOffset,
null, Thread.currentThread()))
return;
try {
Callable<V> c = callable;
// 再次判断任务的状态,避免两次判断状态之间有其他线程对任务状态进行修改
if (c != null && state == NEW) {
V result;
boolean ran;
try {
// 执行任务
result = c.call();
// 执行成功
ran = true;
} catch (Throwable ex) {
result = null;
ran = false;
setException(ex);
}
// 如果执行任务成功
if (ran)
set(result);
}
} finally {
// runner must be non-null until state is settled to
// prevent concurrent calls to run()
runner = null;
// state must be re-read after nulling runner to prevent
// leaked interrupts
int s = state;
if (s >= INTERRUPTING)
handlePossibleCancellationInterrupt(s);
}
}

FutureTask#set(V v)

    protected void set(V v) {
// CAS 将当前任务的状态 从 NEW 转化 为 COMPLETING
if (UNSAFE.compareAndSwapInt(this, stateOffset, NEW, COMPLETING)) {
outcome = v;
// 走到这里只有一个线程会到这里,设置任务状态 为NORMAL 正常结束
UNSAFE.putOrderedInt(this, stateOffset, NORMAL); // final state
finishCompletion();
}
}

FutureTask#setException(Throwable t)

    protected void setException(Throwable t) {
// CAS 将当前任务的状态 从 NEW 转化 为 COMPLETING
if (UNSAFE.compareAndSwapInt(this, stateOffset, NEW, COMPLETING)) {
outcome = t;
// 走到这里只有一个线程会到这里,设置任务状态 为EXCEPTIONAL,非正常结束
UNSAFE.putOrderedInt(this, stateOffset, EXCEPTIONAL); // final state
finishCompletion();
}
}

scheduleWithFixedDelay

针对任务类型为fixed-delay,当任务执行完毕后,让其延迟固定时间后再次运行,原理是:

  1. 当向延迟队列中添加一个任务时,将会等待initialDelay时间,时间到了就过期,从队列中移除,并执行。
  2. 执行完毕之后,会重新设置任务的延迟时间,然后再把任务放入延迟队列,循环。
  3. 如果一个任务在执行过程中抛出了一个异常,任务结束,但不会影响其他任务的执行。
    // initialDelay : 提交任务后延迟多少时间开始执行任务
// delay : 当任务执行完毕后延长多少时间后再次运行任务
public ScheduledFuture<?> scheduleWithFixedDelay(Runnable command,
long initialDelay,
long delay,
TimeUnit unit) {
// 参数校验
if (command == null || unit == null)
throw new NullPointerException();
if (delay <= 0)
throw new IllegalArgumentException();
// 任务转换 period < 0
ScheduledFutureTask<Void> sft =
new ScheduledFutureTask<Void>(command,
null,
triggerTime(initialDelay, unit),
unit.toNanos(-delay));
RunnableScheduledFuture<Void> t = decorateTask(command, sft);
sft.outerTask = t;
// 添加任务到队列
delayedExecute(t);
return t;
}

注意这里构造的ScheduledFutureTask的period<0,会导致boolean periodic = isPeriodic();的结果是true,因此在ScheduledFutureTask的run逻辑中,会调用FutureTask的runAndReset()方法。

ScheduledFutureTask#run()

具体执行任务的线程是Worker线程,任务执行是Worker线程调用任务的润方法执行,这里的任务是ScheduledFutureTask,也就是调用它的run方法。

        public void run() {
// 是否只执行一次 period != 0
boolean periodic = isPeriodic();
// 取消任务
if (!canRunInCurrentRunState(periodic))
cancel(false);
// 任务只执行一次, 调用FutureTask的run
else if (!periodic)
ScheduledFutureTask.super.run();
// 定时执行
else if (ScheduledFutureTask.super.runAndReset()) {
// 设置下一次运行时间
setNextRunTime();
// 重新加入延迟队列
reExecutePeriodic(outerTask);
}
}

FutureTask#runAndReset()

相比于FutureTask的run方法,该方法逻辑差不多,但缺少了:在任务正常执行完后设置状态的步骤。原因在于:让任务成为可重复执行的任务。

    protected boolean runAndReset() {
if (state != NEW ||
!UNSAFE.compareAndSwapObject(this, runnerOffset,
null, Thread.currentThread()))
return false;
boolean ran = false;
int s = state;
try {
Callable<V> c = callable;
if (c != null && s == NEW) {
try {
c.call(); // don't set result
ran = true;
} catch (Throwable ex) {
setException(ex);
}
}
} finally {
// runner must be non-null until state is settled to
// prevent concurrent calls to run()
runner = null;
// state must be re-read after nulling runner to prevent
// leaked interrupts
s = state;
if (s >= INTERRUPTING)
handlePossibleCancellationInterrupt(s);
}
// 如果当前任务正常执行完毕并且任务状态为NEW 则返回true, 否则返回false
return ran && s == NEW;
}

如果该方法返回true,将会调用setNextRunTime()设置下一次的运行时间,接着调用reExecutePeriodic(outerTask)重新加入任务队列。

void setNextRunTime()

		// 设置下一次运行时间
private void setNextRunTime() {
long p = period;
if (p > 0)
time += p;
else
// 延迟-p的时间
time = triggerTime(-p);
}

scheduleAtFixedRate

针对任务类型为fixed-rate,相对起始时间点以固定频率调用指定的任务。

    // initialDelay : 提交任务后延迟多少时间开始执行任务
// period 固定周期
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();
// period > 0
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;
}

它和scheduleWithFixedDelay类似,区别在于:

  1. period>0, 但仍然满足period!=0的条件。
  2. setNextRunTime() 走进time+=p 的分支,而不是 time=triggerTime(-p)。

最终的执行规则为:initialDelay + n * period 的 刻执行任务,如果当前任务执行的时间到了,不会并发执行,下一次执行的任务将会延迟执行。

总结

  • ScheduledThreadPoolExecutor内部使用DelayedWorkQueue存放执行的任务ScheduledFutureTask。
  • ScheduledFutureTask是一个具有返回值的任务,继承自FutureTask。根据period的值分为三类:
    • period == 0 ,当前任务是一次性的,执行完毕后就退出。
    • period > 0 ,当前任务是fixed-delay任务,是固定延迟的定时可重复执行任务。
    • period < 0 ,当前任务是fixed-rate任务,是固定频率的定时可重复执行任务。

参考阅读

  • 《Java并发编程之美》
  • 《疯狂Java讲义》
  • 《Java并发编程的艺术》

Java并发包源码学习系列:线程池ScheduledThreadPoolExecutor源码解析的更多相关文章

  1. Java并发包源码学习之线程池(一)ThreadPoolExecutor源码分析

    Java中使用线程池技术一般都是使用Executors这个工厂类,它提供了非常简单方法来创建各种类型的线程池: public static ExecutorService newFixedThread ...

  2. Java调度线程池ScheduledThreadPoolExecutor源码分析

    最近新接手的项目里大量使用了ScheduledThreadPoolExecutor类去执行一些定时任务,之前一直没有机会研究这个类的源码,这次趁着机会好好研读一下. 该类主要还是基于ThreadPoo ...

  3. MYSQL 源码解读系列 [线程池。。] ----dennis的博客

    http://blog.sina.com.cn/s/articlelist_1182000643_0_1.html

  4. Java并发包源码学习系列:挂起与唤醒线程LockSupport工具类

    目录 LockSupport概述 park与unpark相关方法 中断演示 blocker的作用 测试无blocker 测试带blocker JDK提供的demo 总结 参考阅读 系列传送门: Jav ...

  5. Java并发包源码学习系列:线程池ThreadPoolExecutor源码解析

    目录 ThreadPoolExecutor概述 线程池解决的优点 线程池处理流程 创建线程池 重要常量及字段 线程池的五种状态及转换 ThreadPoolExecutor构造参数及参数意义 Work类 ...

  6. Java并发包源码学习系列:CLH同步队列及同步资源获取与释放

    目录 本篇学习目标 CLH队列的结构 资源获取 入队Node addWaiter(Node mode) 不断尝试Node enq(final Node node) boolean acquireQue ...

  7. Java并发包源码学习系列:AQS共享式与独占式获取与释放资源的区别

    目录 Java并发包源码学习系列:AQS共享模式获取与释放资源 独占式获取资源 void acquire(int arg) boolean acquireQueued(Node, int) 独占式释放 ...

  8. Java并发包源码学习系列:ReentrantLock可重入独占锁详解

    目录 基本用法介绍 继承体系 构造方法 state状态表示 获取锁 void lock()方法 NonfairSync FairSync 公平与非公平策略的差异 void lockInterrupti ...

  9. Java并发包源码学习系列:ReentrantReadWriteLock读写锁解析

    目录 ReadWriteLock读写锁概述 读写锁案例 ReentrantReadWriteLock架构总览 Sync重要字段及内部类表示 写锁的获取 void lock() boolean writ ...

随机推荐

  1. 分布式缓存 — memcache

    MemCache是一个自由.源码开放.高性能.分布式的分布式内存对象缓存系统,用于动态Web应用以减轻数据库的负载.它通过在内存中缓存数据和对象来减少读取数据库的次数,从而提高了网站访问的速度.Mem ...

  2. 引入 Gateway 网关,这些坑一定要学会避开!!!

    Spring cloud gateway是替代zuul的网关产品,基于Spring 5.Spring boot 2.0以上.Reactor, 提供任意的路由匹配和断言.过滤功能.上一篇文章谈了一下Ga ...

  3. (五)整合 Swagger2 ,构建接口管理界面

    整合 Swagger2 ,构建接口管理界面 1.Swagger2简介 1.1 Swagger2优点 1.2 Swagger2常用注解 2.SpringBoot整合Swagger2 2.1 Swagee ...

  4. 关于.NET中迭代器的实现以及集合扩展方法的理解

    在C#中所有的数据结构类型都实现IEnumerable或IEnumerable<T>接口(实现迭代器模式),可以实现对集合遍历(集合元素顺序访问).换句话可以这么说,只要实现上面这两个接口 ...

  5. Jenkins(6)测试报告邮件发送

    前言 前面已经实现在jenkins上展示html的测试报告,接下来只差最后一步,把报告发给你的领导,展示你的劳动成果了. 安装 Email Extension Plugin 插件 jenkins首页- ...

  6. 2019 ICPC Asia Nanjing Regional

    2019 ICPC Asia Nanjing Regional A - Hard Problem 计蒜客 - 42395 若 n = 10,可以先取:6,7,8,9,10.然后随便从1,2,3,4,5 ...

  7. Educational Codeforces Round 90 (Rated for Div. 2) D. Maximum Sum on Even Positions(dp)

    题目链接:https://codeforces.com/contest/1373/problem/D 题意 给出一个大小为 $n$ 的数组 $a$,下标为 $0 \sim n - 1$,可以进行一次反 ...

  8. Educational Codeforces Round 9 C. The Smallest String Concatenation(字符串排序)

    You're given a list of n strings a1, a2, ..., an. You'd like to concatenate them together in some or ...

  9. 1561: (More) Multiplication

    Description Educators are always coming up with new ways to teach math to students. In 2011, an educ ...

  10. Codeforces Round #494 (Div. 3) D. Coins and Queries (贪心,数学)

    题意:给你一组全是\(2^d\ (d\ge0)\)的数,询问q次,每次询问一个数,问这个数是否能够由原数组中的数相加得到,如果能,输出最少用多少个数,否则输出\(-1\). 题解:首先贪心得出结论:如 ...