Java/JUC进阶/Java 并发 - 09 ScheduledThreadPoolExecutor 进阶

目录:

一、ScheduledThreadPoolExecutor 结构概述

  1. 继承关系
  2. ScheduledFutureTask

二、scheduleAtFixedRate 与 scheduleWithFixedDelay

  1. scheduleAtFixedRate
  2. scheduleWithFixedDelay

三、源码分析

  1. 延迟任务
  2. 周期任务
  3. 取消任务

总结

本文主要讲述 ThreadPoolExecutor 一个特殊的子类  ScheduleThreadPoolExecutor , 主要用户执行周期性任务; 所以在看本文

前最好先了解下 https://blog.csdn.net/wszhongguolujun/article/details/100525715 , 另外 ScheduleThreadPoolExecutor 中使用了延迟队列, 主要是基于完全二叉堆实现的, 可以参考完全二叉堆;

一、ScheduledThreadPoolExecutor 结构概述

1. 继承关系

public class ScheduledThreadPoolExecutor 
  extends ThreadPoolExecutor implements ScheduledExecutorService {}

在源码中可以看到, ScheduledThreadPoolExecutor 的状态管理、入队操作、拒绝操作等都是继承于 ThreadPoolExecutor ; ScheduledThreadPoolExecutor 主要提供了周期任务和延迟相关的操作

  • schedule (Runnable command, long delay, TimeUnit unit) // 无返回值的延迟任务
  • schedule(Callable callable, long delay, TimeUnit unit)// 有返回值的延迟任务
  • scheduleAtFixedRate(Runnable command, long initialDelay, long preiod, TimeUnit unit) //固定频率周期任务
  • scheduleWithFixedDelay(Runnable command, long initialDelay, long delay, TimeUnit unit)//固定延迟周期任务

就 ScheduledThreadPoolExecutor 的运行逻辑而言,大致可以表述为:

  • 首先将 Runnable/Callable 封装为 ScheduledFutureTask,延迟时间作为比较属性;
  • 然后加入 DelayedWorkQueue 队列中,每次取出队首延迟最小的任务,超时等待,然后执行;
  • 最后判断是否为周期任务,然后将其重新加入 DelayedWorkQueue 队列中;

其内部结构如图所示:

这里需要注意:

  • ScheduleThreadPoolExecutor 中的队列不能指定,只能是 DelayedWorkQueue ; 因为他是无界队列, 所以在添加任务的时候线程最多可以增加到 coreSize.
  • ScheduleThreadPoolExecutor 重写了 ThreadPoolExecutor execute() 方法, 其执行的核心方法变成  delayedExecutor() ;

2. ScheduledFutureTask

private class ScheduledFutureTask<V> extends FutureTask<V> implements RunnableScheduledFuture<V> {
  private final long sequenceNumber;  // 任务序号,从 AtomicLong sequencer 获取,当延迟时间相同时,序号小的先出
  private long time;                  // 下次任务执行时间
  private final long period;          // 0 表示非周期任务,正值表示固定频率周期任务,负值表示固定延迟周期任务
  RunnableScheduledFuture<V> outerTask = this;  // 重复执行的任务,传入的任务可以使用 decorateTask() 重新包装
  int heapIndex;                      // 队列索引
}

其中最重要的方法必然是run 方法了

public void run() {
  boolean periodic = isPeriodic();    // 是否为周期任务,period != 0
  if (!canRunInCurrentRunState(periodic))  // 当前状态能否继续运行,详细测试后面还会讲到
    cancel(false);     // 取消任务
  else if (!periodic)  // 不是周期任务时,直接运行
    ScheduledFutureTask.super.run();
  else if (ScheduledFutureTask.super.runAndReset()) {  // 时周期任务
    setNextRunTime();              // 设置下次执行时间
    reExecutePeriodic(outerTask);  // 重新入队
  }
}
public boolean cancel(boolean mayInterruptIfRunning) {
  boolean cancelled = super.cancel(mayInterruptIfRunning);  // 设置中断状态
  if (cancelled && removeOnCancel && heapIndex >= 0)        // 当设置 removeOnCancel 状态时,移除任务
    remove(this);                                           // 默认为 false
  return cancelled;
}
void reExecutePeriodic(RunnableScheduledFuture<?> task) {
  if (canRunInCurrentRunState(true)) {  // 如果当前状态可以执行
    super.getQueue().add(task);         // 则重新入队
    if (!canRunInCurrentRunState(true) && remove(task))
      task.cancel(false);
    else ensurePrestart();              // 确保有线程执行任务 
  }
}

 

二、scheduleAtFixedRate 与 scheduleWithFixedDelay

scheduleAtFixedRate 和  scheduleWithFixedDelay  是我们最常用的两个方法, 但是他们的区别可能不是很清楚

1. scheduleAtFixedRate

ScheduledThreadPoolExecutor pool = new ScheduledThreadPoolExecutor(1);
pool.scheduleAtFixedRate(() -> {
  sleep(1000);               // 睡眠 1s,
  log.info("run task");
}, 1, 2, TimeUnit.SECONDS);  // 延迟 1s,周期 2s
[19:41:28,489 INFO ] [pool-1-thread-1] - run task
[19:41:30,482 INFO ] [pool-1-thread-1] - run task
[19:41:32,483 INFO ] [pool-1-thread-1] - run task
[19:41:34,480 INFO ] [pool-1-thread-1] - run task

可以看到固定周期 2s 执行, 但是如果任务执行时间操作周期呢

ScheduledThreadPoolExecutor pool = new ScheduledThreadPoolExecutor(1);
pool.scheduleAtFixedRate(() -> {
  int i = 2000 + random.nextInt(3) * 1000;
  sleep(i);
  log.info("run task, sleep :{}", i);
}, 1, 2, TimeUnit.SECONDS);  // 延迟 1s,周期 2s
[19:42:53,428 INFO ] [pool-1-thread-1] - run task, sleep :2000
[19:42:55,430 INFO ] [pool-1-thread-1] - run task, sleep :2000
[19:42:59,430 INFO ] [pool-1-thread-1] - run task, sleep :4000
[19:43:02,434 INFO ] [pool-1-thread-1] - run task, sleep :3000
[19:43:06,436 INFO ] [pool-1-thread-1] - run task, sleep :4000

可以看到如果任务执行时间超出周期时,下一次任务会立刻运行;就好像周期是一个有弹性的袋子,能装下运行时间的时候,是固定大小,装不下的时候就会被撑大,图像化表示如下:

2. scheduleWithFixedDelay

ScheduledThreadPoolExecutor pool = new ScheduledThreadPoolExecutor(1);
pool.scheduleAtFixedRate(() -> {
  int i = 1000 + random.nextInt(5) * 1000;
  sleep(i);
  log.info("run task, sleep :{}", i);
}, 1, 2, TimeUnit.SECONDS);  // 延迟 1s,周期 2s
[20:05:40,682 INFO ] [pool-1-thread-1] - run task, sleep :1000
[20:05:45,686 INFO ] [pool-1-thread-1] - run task, sleep :3000
[20:05:49,689 INFO ] [pool-1-thread-1] - run task, sleep :2000
[20:05:55,690 INFO ] [pool-1-thread-1] - run task, sleep :4000
[20:06:01,692 INFO ] [pool-1-thread-1] - run task, sleep :4000

可以看到无论执行时间是多少,其结果都是在执行完毕后,停顿固定的时间,然后执行下一次任务,其图形化表示为:

三、源码分析

1. 延迟任务

public void execute(Runnable command) {
  schedule(command, 0, NANOSECONDS);
}

public <T> Future<T> submit(Callable<T> task) {
  return schedule(task, 0, NANOSECONDS);
}

public ScheduledFuture<?> schedule(Runnable command, long delay, TimeUnit unit) {
  if (command == null || unit == null) throw new NullPointerException();
  RunnableScheduledFuture<?> t = decorateTask(
    command,new ScheduledFutureTask<Void>(command, null, triggerTime(delay, unit)));
  delayedExecute(t);
  return t;
}

public <V> ScheduledFuture<V> schedule(Callable<V> callable, long delay, TimeUnit unit) {
  if (callable == null || unit == null) throw new NullPointerException();
  RunnableScheduledFuture<V> t = decorateTask(
    callable, new ScheduledFutureTask<V>(callable, triggerTime(delay, unit)));
  delayedExecute(t);
  return t;
}

可以看到所有周期任务, 最终执行的都是 delayedExecute 方法, 其中 decorateTask 是一个钩子函数,其子类可以利用他对任务进行重构过滤等操作;

private void delayedExecute(RunnableScheduledFuture<?> task) {
  if (isShutdown()) reject(task);  // 如果线程池已经关闭,则拒绝任务
  else {
    super.getQueue().add(task);    // 任务入队
    if (isShutdown() &&            // 再次检查,线程池是否关闭
      !canRunInCurrentRunState(task.isPeriodic()) &&
      remove(task))
      task.cancel(false);
    else
      ensurePrestart();            // 确保有线程执行任务
  }
}

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;
}

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();
  
  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;
}

从上面的代码可以看到  scheduleAtFixedRate 和  scheduleWithFixedDelay 只有周期任务的时间不同,其他的都一样,那么下面我们看一下他们的任务时间计算;

public long getDelay(TimeUnit unit) {
  return unit.convert(time - now(), NANOSECONDS);
}

private void setNextRunTime() {
  long p = period;
  if (p > 0)    // 正值表示 scheduleAtFixedRate
    time += p;  // 不管任务执行时间,直接加上周期时间,也就是一次任务超时,会影响后续任务的执行,
                // 超时的时候,getDelay 是负值,所以在延迟队列中必然排在最前面,立刻被取出执行
  else
    time = triggerTime(-p);  // 计算触发时间
}

long triggerTime(long delay) {  // 这里可以看到,每次的确是在当前时间的基础上,加上延迟时间;
  return now() + ((delay < (Long.MAX_VALUE >> 1)) ? delay : overflowFree(delay));  
}

这里特别要注意 scheduleAtFixedRate 一次任务超时, 会持续影响后面任务周期安排, 所以在设定周期的时候要特别注意

ScheduledThreadPoolExecutor pool = new ScheduledThreadPoolExecutor(1);
pool.scheduleAtFixedRate(() -> {
    int i = random.nextInt(5) * 1000;
    sleep(i);
    log.info("run task, sleep :{}", i);
}, 1, 2, TimeUnit.SECONDS);
[20:29:11,310 INFO ] [pool-1-thread-1] - run task, sleep :1000
[20:29:16,304 INFO ] [pool-1-thread-1] - run task, sleep :4000
[20:29:19,304 INFO ] [pool-1-thread-1] - run task, sleep :3000
[20:29:21,305 INFO ] [pool-1-thread-1] - run task, sleep :2000
[20:29:22,305 INFO ] [pool-1-thread-1] - run task, sleep :1000
[20:29:23,306 INFO ] [pool-1-thread-1] - run task, sleep :1000
[20:29:27,306 INFO ] [pool-1-thread-1] - run task, sleep :4000
[20:29:30,307 INFO ] [pool-1-thread-1] - run task, sleep :3000

3. 取消任务

private volatile boolean continueExistingPeriodicTasksAfterShutdown; //关闭后继续执行周期任务,默认false
private volatile boolean executeExistingDelayedTasksAfterShutdown = true; //关闭后继续执行延迟任务,默认true
private volatile boolean removeOnCancel = false;  // 取消任务是,从队列中删除任务,默认 false

@Override void onShutdown() {
    BlockingQueue<Runnable> q = super.getQueue();
    boolean keepDelayed = getExecuteExistingDelayedTasksAfterShutdownPolicy();    // 继续延迟任务
    boolean keepPeriodic = getContinueExistingPeriodicTasksAfterShutdownPolicy(); // 继续周期任务
    if (!keepDelayed && !keepPeriodic) {  // 都是 false,直接清除
        for (Object e : q.toArray())
            if (e instanceof RunnableScheduledFuture<?>)
                ((RunnableScheduledFuture<?>) e).cancel(false);
        q.clear();
    }
    else {
        // Traverse snapshot to avoid iterator exceptions
        for (Object e : q.toArray()) {
            if (e instanceof RunnableScheduledFuture) {
                RunnableScheduledFuture<?> t = (RunnableScheduledFuture<?>)e;
                if ((t.isPeriodic() ? !keepPeriodic : !keepDelayed) ||
                    t.isCancelled()) { // also remove if already cancelled
                    if (q.remove(t))
                        t.cancel(false);
                }
            }
        }
    }
    tryTerminate();
}

总结

  • scheduleAtFixedRate : 固定频率周期任务, 注意一次任务超时,会持续的影响后续的任务周期;
  • scheduleWithFixedDelay : 固定延迟周期任务, 即每次任务结束后, 超时等待固定时间;
  • 此外 ScheduledThreadPoolExecutor : 线程最多为核心线程,最大线程数不起作用,因为 DelayedWorkQueue 是无界队列
CSDN海神之光上传的代码均可运行,亲测可用,直接替换数据即可,适合小白; 1、代码压缩包内容 主函数:main.m; 调用函数:其他m文件;无需运行 运行结果效果图; 2、代码运行版本 Matlab 2019b或2023b;若运行有误,根据提示修改;若不会,私信博主; 3、运行操作步骤 步骤一:将所有文件放到Matlab的当前文件夹中; 步骤二:双击打开main.m文件; 步骤三:点击运行,等程序运行完得到结果; 4、仿真咨询 如需其他服务,可私信博主或扫描博客文章底部QQ名片; 4.1 博客或资源的完整代码提供 4.2 期刊或参考文献复现 4.3 Matlab程序定制 4.4 科研合作 功率谱估计: 故障诊断分析: 雷达通信:雷达LFM、MIMO、成像、定位、干扰、检测、信号分析、脉冲压缩 滤波估计:SOC估计 目标定位:WSN定位、滤波跟踪、目标定位 生物电信号:肌电信号EMG、脑电信号EEG、心电信号ECG 通信系统:DOA估计、编码译码、变分模态分解、管道泄漏、滤波器、数字信号处理+传输+分析+去噪(CEEMDAN)、数字信号调制、误码率、信号估计、DTMF、信号检测识别融合、LEACH协议、信号检测、水声通信 1. EMD(经验模态分解,Empirical Mode Decomposition) 2. TVF-EMD(时变滤波的经验模态分解,Time-Varying Filtered Empirical Mode Decomposition) 3. EEMD(集成经验模态分解,Ensemble Empirical Mode Decomposition) 4. VMD(变分模态分解,Variational Mode Decomposition) 5. CEEMDAN(完全自适应噪声集合经验模态分解,Complementary Ensemble Empirical Mode Decomposition with Adaptive Noise) 6. LMD(局部均值分解,Local Mean Decomposition) 7. RLMD(鲁棒局部均值分解, Robust Local Mean Decomposition) 8. ITD(固有时间尺度分解,Intrinsic Time Decomposition) 9. SVMD(逐次变分模态分解,Sequential Variational Mode Decomposition) 10. ICEEMDAN(改进的完全自适应噪声集合经验模态分解,Improved Complementary Ensemble Empirical Mode Decomposition with Adaptive Noise) 11. FMD(特征模式分解,Feature Mode Decomposition) 12. REMD(鲁棒经验模态分解,Robust Empirical Mode Decomposition) 13. SGMD(辛几何模态分解,Spectral-Grouping-based Mode Decomposition) 14. RLMD(鲁棒局部均值分解,Robust Intrinsic Time Decomposition) 15. ESMD(极点对称模态分解, extreme-point symmetric mode decomposition) 16. CEEMD(互补集合经验模态分解,Complementary Ensemble Empirical Mode Decomposition) 17. SSA(奇异谱分析,Singular Spectrum Analysis) 18. SWD(群分解,Swarm Decomposition) 19. RPSEMD(再生相移正弦辅助经验模态分解,Regenerated Phase-shifted Sinusoids assisted Empirical Mode Decomposition) 20. EWT(经验小波变换,Empirical Wavelet Transform) 21. DWT(离散小波变换,Discraete wavelet transform) 22. TDD(时域分解,Time Domain Decomposition) 23. MODWT(最大重叠离散小波变换,Maximal Overlap Discrete Wavelet Transform) 24. MEMD(多元经验模态分解,Multivariate Empirical Mode Decomposition) 25. MVMD(多元变分模态分解,Multivariate Variational Mode Decomposition)
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

阿里极风

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值