定时线程池执行器

一、基础知识的补充

@Slf4j
public class ConditionTest {
    private static ReentrantLock reentrantLock = new ReentrantLock();
    private static final Condition available = reentrantLock.newCondition();
    public static void main(String[] args) {
        for(int i = 0 ; i < 10; i++){
            int finalI = i;
            new Thread(()->{
                reentrantLock.lock();
                try {
                    System.out.println("正在执行" + finalI);
                    available.await(1, TimeUnit.MINUTES);
                    System.out.println("执行完成....");
                }catch (Exception e){
                    e.printStackTrace();
                }finally {
                    System.out.println("解锁");
                    reentrantLock.unlock();
                }
            }).start();
        }
    }
}

执行结果:

正在执行0
正在执行1
正在执行9
正在执行2
正在执行3
正在执行4
正在执行5
正在执行6
正在执行7
正在执行8
执行完成....
解锁
执行完成....
解锁
执行完成....
解锁
执行完成....
解锁
执行完成....
解锁
执行完成....
解锁
执行完成....
解锁
执行完成....
解锁
执行完成....
解锁
执行完成....
解锁

Process finished with exit code 0

得出的结论:await()方法挂起当前线程并释放锁

二、定时线程池类的类结构图

 它用来处理延时任务或定时任务。

它接收SchduledFutureTask类型的任务,是线程池调度任务的最小单位,有三种提交任务的方式:

  1. schedule
  2. scheduledAtFixedRate
  3. scheduledWithFixedDelay

它采用DelayQueue存储等待的任务

  1. DelayQueue内部封装了一个PriorityQueue,它会根据time的先后时间排序,若time相同则根据sequenceNumber排序;
  2. DelayQueue也是一个无界队列;

三、SchduledFutureTask的源码解析

SchduledFutureTask接收的参数(成员变量):

private long time:任务开始的时间
private final long sequenceNumber;:任务的序号
private final long period:任务执行的时间间隔

工作线程的执行过程:

  • 工作线程会从DelayQueue取已经到期的任务去执行;
  • 执行结束后重新设置任务的到期时间,再次放回DelayQueue

ScheduledThreadPoolExecutor会把待执行的任务放到工作队列DelayQueue中,DelayQueue封装了一个PriorityQueue,PriorityQueue会对队列中的ScheduledFutureTask进行排序,具体的排序算法实现如下:

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;
		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;
}
  1. 首先按照time排序,time小的排在前面,time大的排在后面;
  2. 如果time相同,按照sequenceNumber排序,sequenceNumber小的排在前面,sequenceNumber大的排在后面,换句话说,如果两个task的执行时间相同,优先执行先提交的task。

SchduledFutureTask之run方法实现

run方法是调度task的核心,task的执行实际上是run方法的执行。

public void run() {
    boolean periodic = isPeriodic();
    //如果当前线程池已经不支持执行任务,则取消
    if (!canRunInCurrentRunState(periodic))
        cancel(false);
    //如果不需要周期性执行,则直接执行run方法然后结束
    else if (!periodic)
        ScheduledFutureTask.super.run();
    //如果需要周期执行,则在执行完任务以后,设置下一次执行时间
    else if (ScheduledFutureTask.super.runAndReset()) {
        // 计算下次执行该任务的时间
        setNextRunTime();
        //重复执行任务
        reExecutePeriodic(outerTask);
    }
}
  1. 如果当前线程池运行状态不可以执行任务,取消该任务,然后直接返回,否则执行步骤2;
  2. 如果不是周期性任务,调用FutureTask中的run方法执行,会设置执行结果,然后直接返回,否则执行步骤3;
  3. 如果是周期性任务,调用FutureTask中的runAndReset方法执行,不会设置执行结果,然后直接返回,否则执行步骤4和步骤5;
  4. 计算下次执行该任务的具体时间;
  5. 重复执行任务。

reExecutePeriodic方法

void reExecutePeriodic(RunnableScheduledFuture<?> task) {
    if (canRunInCurrentRunState(true)) {
        super.getQueue().add(task);
        if (!canRunInCurrentRunState(true) && remove(task))
            task.cancel(false);
        else
            ensurePrestart();
    }
}

该方法和delayedExecute方法类似,不同的是:

  1. 由于调用reExecutePeriodic方法时已经执行过一次周期性任务了,所以不会reject当前任务;
  2. 传入的任务一定是周期性任务。

线程池任务的提交

首先是schedule方法,该方法是指任务在指定延迟时间到达后触发,只会执行一次。

public ScheduledFuture<?> schedule(Runnable command,
                                   long delay,
                                   TimeUnit unit) {
    //参数校验
    if (command == null || unit == null)
        throw new NullPointerException();
    //这里是一个嵌套结构,首先把用户提交的任务包装成ScheduledFutureTask
    //然后在调用decorateTask进行包装,该方法是留给用户去扩展的,默认是个空方法
    RunnableScheduledFuture<?> t = decorateTask(command,
            new ScheduledFutureTask<Void>(command, null,
                                      triggerTime(delay, unit)));
    //包装好任务以后,就进行提交了
    delayedExecute(t);
    return t;
}

任务提交方法:

private void delayedExecute(RunnableScheduledFuture<?> task) {
    //如果线程池已经关闭,则使用拒绝策略把提交任务拒绝掉
    if (isShutdown())
        reject(task);
    else {
    //与ThreadPoolExecutor不同,这里直接把任务加入延迟队列
        super.getQueue().add(task);//使用用的DelayedWorkQueue
    //如果当前状态无法执行任务,则取消
        if (isShutdown() &&
            !canRunInCurrentRunState(task.isPeriodic()) &&
            remove(task))
            task.cancel(false);
        else
    //这里是增加一个worker线程,避免提交的任务没有worker去执行
    //原因就是该类没有像ThreadPoolExecutor一样,woker满了才放入队列
          ensurePrestart();
    }
}

DelayedWorkQueue

        ScheduledThreadPoolExecutor之所以要自己实现阻塞的工作队列,是因为ScheduledThreadPoolExecutor要求的工作队列有些特殊。

        DelayedWorkQueue是一个基于堆的数据结构,类似于DelayQueue和PriorityQueue。在执行定时任务的时候,每个任务的执行时间都不同,所以DelayedWorkQueue的工作就是按照执行时间的升序来排列,执行时间距离当前时间越近的任务在队列的前面(注意:这里的顺序并不是绝对的,堆中的排序只保证了子节点的下次执行时间要比父节点的下次执行时间要大,而叶子节点之间并不一定是顺序的 )。

四、扩展:

xxljob:

集成xxljob的项目,首先会有这么一段配置:

 @Bean
    public XxlJobSpringExecutor xxlJobExecutor() {
        logger.info(">>>>>>>>>>> xxl-job config init.");
        XxlJobSpringExecutor xxlJobSpringExecutor = new XxlJobSpringExecutor();
        xxlJobSpringExecutor.setAdminAddresses(adminAddresses);
        xxlJobSpringExecutor.setAppname(appname);
        xxlJobSpringExecutor.setAddress(address);
        xxlJobSpringExecutor.setIp(ip);
        xxlJobSpringExecutor.setPort(port);
        xxlJobSpringExecutor.setAccessToken(accessToken);
        xxlJobSpringExecutor.setLogPath(logPath);
        xxlJobSpringExecutor.setLogRetentionDays(logRetentionDays);

        return xxlJobSpringExecutor;
    }

XxlJobSpringExecutor 实现了SmartInitializingSingleton的接口,当所有单例 bean 都初始化完成以后, Spring的IOC容器会回调该接口的 afterSingletonsInstantiated()方法:

1、会将带有XxlJob注解的方法注册到容器中,key为XxlJob的value,value为MethodJobHandler。

2、使用了Netty的Http通信,然后启动了服务器端

xxljob-admin会和服务端进行通信:

1、创建一个线程scheduleThread,执行调度任务,是daemon线程:

1) 使用悲观锁,进行分布式锁,这样即时有多个xxljob-admin也只会有一台执行:

select * from xxl_job_lock where lock_name = 'schedule_lock' for update

2)获取小于等于下次执行时间在5秒后的任务

SELECT <include refid="Base_Column_List" />
		FROM xxl_job_info AS t
		WHERE t.trigger_status = 1
			and t.trigger_next_time <![CDATA[ <= ]]> #{maxNextTime}
		ORDER BY id ASC
		LIMIT #{pagesize}

3)然后判断 当前任务的时间是否在5秒内,如果不在则放弃执行,

当前是10:00:00, trigger_next_time = 9:59:54 则直接放弃,其他情况将需要执行的作业根据执行时间取模并写入到一个Map结构中,即ringData

int ringSecond = (int)((jobInfo.getTriggerNextTime()/1000)%60);
List<Integer> ringItemData = ringData.get(ringSecond);
if (ringItemData == null) {
    ringItemData = new ArrayList<Integer>();
    ringData.put(ringSecond, ringItemData);
}
ringItemData.add(jobId);

2、创建一个线程ringThread

不断循环获取ringItemData集合的数据,通过线程池执行任务:通过jobId获取任务,然后根据任务的jobGroup获取XxlJobGroup

SELECT <include refid="Base_Column_List" />
		FROM xxl_job_info AS t
		WHERE t.id = #{id}
SELECT <include refid="Base_Column_List" />
		FROM xxl_job_group AS t
		WHERE t.id = #{id}

向xxl-job-admin发执行任务的消息http://adress/run,请求参数:

TriggerParam triggerParam = new TriggerParam();
triggerParam.setJobId(jobInfo.getId());
triggerParam.setExecutorHandler(jobInfo.getExecutorHandler());
triggerParam.setExecutorParams(jobInfo.getExecutorParam());
triggerParam.setExecutorBlockStrategy(jobInfo.getExecutorBlockStrategy());
triggerParam.setExecutorTimeout(jobInfo.getExecutorTimeout());
triggerParam.setLogId(jobLog.getId());
triggerParam.setLogDateTime(jobLog.getTriggerTime().getTime());
triggerParam.setGlueType(jobInfo.getGlueType());
triggerParam.setGlueSource(jobInfo.getGlueSource());
triggerParam.setGlueUpdatetime(jobInfo.getGlueUpdatetime().getTime());
triggerParam.setBroadcastIndex(index);
triggerParam.setBroadcastTotal(total);

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
在Spring Boot中,你可以使用`@EnableScheduling`注解来启用定时任务功能,并使用`@Scheduled`注解来指定定时任务的执行时间。 首先,在你的Spring Boot应用主类上添加`@EnableScheduling`注解: ```java import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; import org.springframework.scheduling.annotation.EnableScheduling; @SpringBootApplication @EnableScheduling public class YourApplication { public static void main(String[] args) { SpringApplication.run(YourApplication.class, args); } } ``` 然后,在你想要执行定时任务的方法上添加`@Scheduled`注解,指定任务的执行时间表达式: ```java import org.springframework.scheduling.annotation.Scheduled; import org.springframework.stereotype.Component; @Component public class YourScheduledTask { @Scheduled(cron = "0 0 0 * * ?") // 每天凌晨执行 public void yourTask() { // 执行你的任务逻辑 } } ``` 上述示例中的`@Scheduled(cron = "0 0 0 * * ?")`表示每天凌晨执行任务。你可以根据自己的需求,调整cron表达式来指定不同的执行时间。 此外,Spring Boot还提供了`ThreadPoolTaskScheduler`类来管理定时任务的线程池。你可以在配置类中创建并配置该线程池,然后注入到定时任务中使用。以下是一个示例: ```java import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.scheduling.annotation.EnableScheduling; import org.springframework.scheduling.annotation.SchedulingConfigurer; import org.springframework.scheduling.concurrent.ThreadPoolTaskScheduler; import org.springframework.scheduling.config.ScheduledTaskRegistrar; @Configuration @EnableScheduling public class SchedulingConfiguration implements SchedulingConfigurer { @Bean public ThreadPoolTaskScheduler taskScheduler() { ThreadPoolTaskScheduler scheduler = new ThreadPoolTaskScheduler(); scheduler.setPoolSize(10); // 设置线程池大小 scheduler.setThreadNamePrefix("YourScheduledTask-"); return scheduler; } @Override public void configureTasks(ScheduledTaskRegistrar taskRegistrar) { taskRegistrar.setTaskScheduler(taskScheduler()); } } ``` 在上述示例中,我们创建了一个名为`taskScheduler`的线程池,并设置了线程池的大小和线程名前缀。然后,在`configureTasks()`方法中将该线程池配置到定时任务注册中。 这样,你就可以在定时任务方法中使用该线程池执行并发的任务,确保任务执行的效率和稳定性。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值