Java定时任务线程池ScheduledThreadPoolExecutor

本文深入探讨Java中的ScheduledThreadPoolExecutor,一种用于执行定时任务的线程池。讲解了其核心方法如scheduleAtFixedRate和scheduleWithFixedDelay的使用,分析了源码中关键的延迟队列和执行流程,对比了不同定时任务的区别。

Java并发定时任务线程池--------定时任务ScheduledThreadPoolExecutor

我们了解的ThreadPoolExecutor是java的普通线程池,而ScheduledThreadPoolExecutor是java提供的定时任务线程池。今天就跟大家谈一下我对定时线程池ScheduledThreadPoolExecutor的理解。

ScheduledThreadPoolExecutor的使用
常用
java.util.concurrent.ScheduledThreadPoolExecutor#schedule 定时任务
java.util.concurrent.ScheduledThreadPoolExecutor#scheduleAtFixedRate 固定速率连续执行
java.util.concurrent.ScheduledThreadPoolExecutor#scheduleWithFixedDelay非固定速率连续执行
java.util.concurrent.ScheduledThreadPoolExecutor.DelayedWorkQueue延迟队列
源码分析:

在这里插入图片描述

初始化ScheduledThreadPoolExecutor

调度核心构造器

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

scheduleAtFixedRate方法

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

delayedExecute#ScheduledThreadPoolExecutor方法

if (isShutdown())
    reject(task);
else {
    super.getQueue().add(task);//增加任务 子类实现了
//总结:add方法是通过DelayedWorkQueue(初始化时候指定的队列) 延迟队列实现 offer获取对象的延迟

    if (isShutdown() &&
        !canRunInCurrentRunState(task.isPeriodic()) && //判断是否已经停止
        remove(task))
        task.cancel(false);
    else
        ensurePrestart();
}

Offer#DelayedWorkQueue 二叉树堆排序算法

public boolean offer(Runnable x) {
    if (x == null)
        throw new NullPointerException();
    RunnableScheduledFuture e = (RunnableScheduledFuture)x; //内部类
    final ReentrantLock lock = this.lock;
    lock.lock();
    try {
        int i = size;
        if (i >= queue.length)//判断是否扩容
            grow();
        size = i + 1;
        if (i == 0) {
            queue[0] = e;//这个队列是我们核心
            setIndex(e, 0); //第一个直接设置索引和下标0
        } else {
            siftUp(i, e);//看这儿 
        }
        if (queue[0] == e) {
            leader = null;
            available.signal();//唤醒 }
    } finally {
        lock.unlock();
    }
    return true;
}

siftUp#DelayedWorkQueue保证相同的

while (k > 0) {
    int parent = (k - 1) >>> 1; 
    RunnableScheduledFuture<?> e = queue[parent];
    if (key.compareTo(e) >= 0)
        break; 
    queue[k] = e;
    setIndex(e, k);
    k = parent;
}
queue[k] = key;
setIndex(key, k);

compareTo#ScheduledFutureTask

if (other == this) // compare zero if same object
    return 0;
if (other instanceof ScheduledFutureTask) {
    ScheduledFutureTask<?> x = (ScheduledFutureTask<?>)other;
    long diff = time - x.time; //判断time
    if (diff < 0)
        return -1;
    else if (diff > 0)
        return 1;
    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;

我们在回到开始的地方,根据刚才我们跟代码可以看到执行时间的顺序已经分配好了,那如何确保work可以运行了?
确保有work执行

ensurePrestart#ThreadPoolExecutor

int wc = workerCountOf(ctl.get());
if (wc < corePoolSize)
    addWorker(null, true);
else if (wc == 0)
    addWorker(null, false);

放到队列 runwork take对象

take#DelayedWorkQueue

//调用start>run>runWorker->getTask>take方法

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(TimeUnit.NANOSECONDS);//到时间了
                if (delay <= 0)//到时间了
                    return finishPoll(first);
                else if (leader != null)
                    available.await();//因为没有执行线程初始化,所以等等什么时候有了自己被他人唤醒
                else {
                    Thread thisThread = Thread.currentThread();
                    leader = thisThread;
                    try {
                        available.awaitNanos(delay);//各种condition的awaitNanos 带时间的
                    } finally {
                        if (leader == thisThread)
                            leader = null;
                    }
                }
            }
        }
    } finally {
        if (leader == null && queue[0] != null)
            available.signal();
        lock.unlock();
    }
}

finishPoll#ScheduledThreadPoolExecutor

private RunnableScheduledFuture finishPoll(RunnableScheduledFuture f) {
    int s = --size;
    RunnableScheduledFuture x = queue[s];//重新排列
    queue[s] = null;
    if (s != 0)
        siftDown(0, x);
    setIndex(f, -1);
    return f;
}
区别scheduleAtFixedRate 和scheduleWithFixedDelay 有什么区别吗?

(构造方法中实现了ScheduledFutureTask)

run#ScheduledFutureTask

public void run() {
    boolean periodic = isPeriodic(); //判断是否是周期的
    if (!canRunInCurrentRunState(periodic))
        cancel(false);
    else if (!periodic)
        ScheduledFutureTask.super.run();
    else if (ScheduledFutureTask.super.runAndReset()) {
        setNextRunTime();//点进去
        reExecutePeriodic(outerTask);// 重置task 没有异常捕捉
    }
}

setNextRunTime#ScheduledFutureTask

long p = period;
if (p > 0)  //看这个大于0 和小于0的区别
    time += p;  //假如延迟了这个时间早过了,+当前时候肯定还是过的。
else
    time = triggerTime(-p); //取的当前的任务延迟

参数
task–这是被调度的任务。
delay–这是以毫秒为单位的延迟之前的任务执行。
period–这是在连续执行任务之间的毫秒的时间。

实战:

实战与工作中注意事项
有异常一定要捕获,要不job不会执行了

单列模式(Double check lock)

双重锁定
发布与逸出这块以单列模式举例,在高并发下如何实现一个线程安全的单列模式。
会涉及到这些知识点1、懒汉模式、2、饿汉模式、3、synchronized 4、volatile 5、枚举、6重排序等
内部类
枚举类

AbstractQueuedSynchronizer(AQS)同步器:

公平锁、非公平锁
NonfairSync

cpu 随机调用task

在这里插入图片描述
在这里插入图片描述

总体来说,ScheduedThreadPoolExecutor的重点是要理解下次执行时间的计算,以及优 先队列的出队、入队和删除的过程,这两个是理解ScheduedThreadPoolExecutor的关 键。

若依是基于 Spring Boot 和 Spring Cloud 构建的开源项目,其定时任务线程池配置问题可参考 Spring 框架的相关配置方法。以下是几种可能的解决方法: #### 利用 SchedulingConfigurer 接口配置线程池 可以通过实现 `SchedulingConfigurer` 接口来定制定时任务线程池。示例代码如下: ```java import org.springframework.context.annotation.Configuration; import org.springframework.scheduling.annotation.SchedulingConfigurer; import org.springframework.scheduling.config.ScheduledTaskRegistrar; import java.util.concurrent.Executors; import java.util.concurrent.ScheduledExecutorService; @Configuration public class MySchedulingConfigurer implements SchedulingConfigurer { @Override public void configureTasks(ScheduledTaskRegistrar taskRegistrar) { // 设定长度为 5 的定时任务线程池 ScheduledExecutorService scheduledExecutorService = Executors.newScheduledThreadPool(5); taskRegistrar.setScheduler(scheduledExecutorService); } } ``` 此配置会创建一个大小为 5 的定时任务线程池,从而让多个定时任务能够并行执行[^2]。 #### 使用 ThreadPoolTaskScheduler 配置线程池 还能使用 `ThreadPoolTaskScheduler` 来配置线程池,示例代码如下: ```java import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.scheduling.TaskScheduler; import org.springframework.scheduling.annotation.SchedulingConfigurer; import org.springframework.scheduling.concurrent.ThreadPoolTaskScheduler; import org.springframework.scheduling.config.ScheduledTaskRegistrar; @Configuration public class ScheduleConfig implements SchedulingConfigurer { @Override public void configureTasks(ScheduledTaskRegistrar taskRegistrar) { taskRegistrar.setScheduler(taskScheduler()); } @Bean public TaskScheduler taskScheduler() { ThreadPoolTaskScheduler taskScheduler = new ThreadPoolTaskScheduler(); taskScheduler.setPoolSize(4); taskScheduler.setThreadNamePrefix("scheduler-pool-"); return taskScheduler; } } ``` 该配置会创建一个大小为 4 的线程池,并且给线程设置了前缀 `scheduler-pool-`,方便进行日志跟踪和调试[^3]。 #### 自定义定时任务线程池 可以通过工具类创建自定义的定时任务线程池,示例代码如下: ```java import java.util.concurrent.*; public class CustomScheduledThreadPoolExample { public static void main(String[] args) { // 创建自定义定时任务线程池 ScheduledThreadPoolExecutor scheduledThreadPool = ThreadPoolUtil.createScheduledThreadPool(3, "CustomScheduledThreadPool-"); // 提交任务到定时任务线程池 scheduledThreadPool.scheduleAtFixedRate(() -> { System.out.println("Hello from scheduled task! Running on thread: " + Thread.currentThread().getName()); try { Thread.sleep(1000); // 模拟任务执行 } catch (InterruptedException e) { Thread.currentThread().interrupt(); } }, 0, 1, TimeUnit.SECONDS); // 每隔一秒执行一次 // 关闭线程池 scheduledThreadPool.shutdown(); try { scheduledThreadPool.awaitTermination(5, TimeUnit.SECONDS); } catch (InterruptedException e) { Thread.currentThread().interrupt(); } } } ``` 此示例创建了一个大小为 3 的自定义定时任务线程池,并且提交了一个每隔一秒执行一次的任务[^1]。
评论 1
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值