10-ScheduledThreadPool应用与源码分析

一、ScheduledThreadPoolExecutor介绍&应用

ScheduledThreadPoolExecutor是ThreadPoolExecutor的一个子类,在线程池的基础上实现了延迟执行任务以及周期性执行任务的功能。

Java最早提供的是Timer类执行定时任务,串行的,不靠谱,会影响到其他任务的执行。不采用第三方框架时,用ScheduledThreadPoolExecutor比较合适。

Quartz

xx-job

elastic-job

ScheduledThreadPoolExecutor就是在线程池的基础上实现的定时执行任务的功能。

ScheduledThreadPoolExecutor提供了比较常用的四种方法执行任务:不说callable

  • execute: 跟普通线程池执行没区别
  • schedule: 可以指定延迟时间,一次性执行任务。
  • scheduleAtFixedRate: 可以让任务在固定的周期下执行。(任务的处理时间,不影响下次执行时间)。
    如果任务的执行时间超过了设置的延迟时间,按照时间最长的计算
  • scheduleWithFixedDelay:可以让任务在固定的周期下执行 。(任务的处理时间,会影响下次执行时间)

执行案例演示:

java 复制代码
 public static void main(String[] args) {        ScheduledThreadPoolExecutor scheduledThreadPoolExecutor=new 
ScheduledThreadPoolExecutor(10);        scheduledThreadPoolExecutor.execute(()->{            System.out.println("hello world");        });        scheduledThreadPoolExecutor.schedule(()->{            System.out.println("hello world1");        },2000, TimeUnit.MILLISECONDS);//        scheduledThreadPoolExecutor.scheduleAtFixedRate(()->{//            System.out.println("hello world2:"+System.currentTimeMillis());//            try {//                Thread.sleep(1000);//            } catch (InterruptedException e) {//                e.printStackTrace();//            }//        },3000,2000,TimeUnit.MILLISECONDS);        scheduledThreadPoolExecutor.scheduleWithFixedDelay(()->{            try {                Thread.sleep(1000);            } catch (InterruptedException e) {                throw new RuntimeException(e);            }            System.out.println("hello world3"+System.currentTimeMillis());        },1000,2000,TimeUnit.MILLISECONDS);    }

如果实际开发应用需要使用到定时任务,更推荐一些开源的框架,比如quartz,xx-job,elastic-job,因为corn表达式,对时间的控制更佳方便。

二、synchronousQueue底层结构

两个核心内容:

2.1 scheduledFutureTask

看到了核心内容,scheduledFutureTask间接的实现了Delay接口,让任务可以放到延迟队列中,并且基于二叉堆做排序,即将执行的时间越短,就往堆顶扔。

查看核心内容:

java 复制代码
private class ScheduledFutureTask<V>
            extends FutureTask<V> implements RunnableScheduledFuture<V> {

        //计数器,每个任务进来时,都会有一个全局唯一的序号
		//如果任务的执行时间一模一样,比对sequenceNumber
        private final long sequenceNumber;
		//任务执行的时间,单位是纳秒
        private volatile long time;
        //period ==0:标识一次性执行的任务
		//period > 0:标识使用的是At
		//period < 0:标识使用的是witch
        private final long period;
		//周期性实现任务时,引用具体任务,方便后面重新扔到阻塞队列
        RunnableScheduledFuture<V> outerTask = this;
        //有参构造,schedule时使用当前有参重载封装任务
        ScheduledFutureTask(Runnable r, V result, long triggerTime,
                            long sequenceNumber) {
            super(r, result);
            this.time = triggerTime;
            this.period = 0;
            this.sequenceNumber = sequenceNumber;
        }
		//At,with时,使用当前有参重载封装任务
        ScheduledFutureTask(Runnable r, V result, long triggerTime,
                            long period, long sequenceNumber) {
            super(r, result);
            this.time = triggerTime;
            this.period = period;
            this.sequenceNumber = sequenceNumber;
        }
		//有返回结果,不考虑
        ScheduledFutureTask(Callable<V> callable, long triggerTime,
                            long sequenceNumber) {
            super(callable);
            this.time = triggerTime;
            this.period = 0;
            this.sequenceNumber = sequenceNumber;
        }
		//重写delay接口方法
		 public long getDelay(TimeUnit unit) {
            return unit.convert(time - System.nanoTime(), NANOSECONDS);
        }

        //实现delayed接口重写方法,比较的方式,数据放到二叉堆内部
        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;
        }
		
		//判断是否是周期执行
		public boolean isPeriodic() {
            return period != 0;
        }
2.2 DelayedWorkQueue

已学过,本次忽略

三、schedule方法分析

封装任务-放延迟队列-创建线程准备执行

将传入的command任务和延迟执行的时间封装

java 复制代码
//分析定时任务线程的schedule,延迟一段时间,执行一次command任务
public ScheduledFuture<?> schedule(Runnable command,
                                       long delay,
                                       TimeUnit unit) {
       //分开判断									      
        if (command == null || unit == null)
            throw new NullPointerException();
		//	封装任务,将普通的command封装为ScheduledFutureTask
		//decorateTask 方法默认啥都没做,是线程池提供的扩展方法,可以在此位置修改任务需要执行的方式
        RunnableScheduledFuture<Void> t = decorateTask(command,
            new ScheduledFutureTask<Void>(command, null,
                                          triggerTime(delay, unit),
                                          sequencer.getAndIncrement()));
        //延迟执行
		delayedExecute(t);
        return t;
    }
	

private long triggerTime(long delay, TimeUnit unit) {
        return triggerTime(unit.toNanos((delay < 0) ? 0 : delay));
    }
//查看triggerTime方法重载,返回当前任务要执行的系统时间
long triggerTime(long delay) {
   //判断延迟delay时间是否小于LONG.MAX_VALUE>>1,
   //如果小于,正常计算执行的时间
   //如果大于,可能出现超出long的取值范围问题,做额外处理
        return System.nanoTime() +
            ((delay < (Long.MAX_VALUE >> 1)) ? delay : overflowFree(delay));
    }
	
	//将command任务封装为scheduleFutrueTask
ScheduledFutureTask(Runnable r, V result, long triggerTime,
                            long sequenceNumber) {
			//任务要执行的系统时间
            super(r, result);
            this.time = triggerTime;
			//任务是否是周期性执行
            this.period = 0;
			//基于atomic
            this.sequenceNumber = sequenceNumber;
        }
//	是线程池提供的扩展方法,可以在此位置修改任务需要执行的细节	
protected <V> RunnableScheduledFuture<V> decorateTask(
        Runnable runnable, RunnableScheduledFuture<V> task) {
        return task;
    }
	

//延迟执行
 private void delayedExecute(RunnableScheduledFuture<?> task) {
     //线程停止,是则拒绝执行
        if (isShutdown())
            reject(task);
        else {
		//线程池状态是RUNNING
		//调用阻塞队列,将任务添加进去,将任务扔到延迟队列中(二叉堆)
		//在添加任务到延迟队列的数组时,会记录当前任务所在的索引位置,方便取消任务时,从数组中去除
        //heapIndex方便取消任务         
		 super.getQueue().add(task);
		 //判断线程池是否不是running状态,如果不是running,就根据策略决定任务是否执行
		 //如果任务不需要执行了,调用remove方法,将任务从延迟队列移除,并且在if内部取消任务
            if (!canRunInCurrentRunState(task) && remove(task))
                task.cancel(false);
            else
			//线程池状态没毛病,任务是需要执行的。
                ensurePrestart();
        }
    }
	
 //如果任务添加到阻塞队列中,忽然线程池不是running状态,此时任务是否执行
 //periodic-true:周期性执行的任务,false-代表一次性延迟的任务 
 boolean canRunInCurrentRunState(RunnableScheduledFuture<?> task) {
       //判断当前线程池状态
        if (!isShutdown())
            return true;
        if (isStopped())
            return false;
        return task.isPeriodic()
            ? continueExistingPeriodicTasksAfterShutdown
            : (executeExistingDelayedTasksAfterShutdown
               || task.getDelay(NANOSECONDS) <= 0);
		//默认情况下,如果任务扔到了延迟队列中,有两个策略
      //如果任务是周期性执行的,默认为false
      //如果任务是一次性执行的,默认为true	  
    }
	
//准备执行任务
void ensurePrestart() {
     //获取线程池中的工作线程个数
        int wc = workerCountOf(ctl.get());
		//如果工作线程个数,小于核心线程个数
        if (wc < corePoolSize)
		//创建核心线程
            addWorker(null, true);
		//如果工作线程池不小于核心线程,但是值为0,创建非核心线程执行任务	
        else if (wc == 0)
		//创建非核心线程处理阻塞队列任务,而且只要阻塞队列没有任务了,当前线程立即消耗 
            addWorker(null, false);
    }

查看任务放到延迟队列后,是如何被工作线程取出来执行的。

执行addWorker方法,会创建一个工作线程,工作线程在创建成功后,会执行start方法。在start方法执行后会调用worker的run方法,最终执行了runWorker方法,在runworker方法中会在阻塞队列的位置执行一直阻塞拿Ruable方法,拿到返回,就执行。

所以需要查看的就是延迟队列的take方法,套路和之前的delayQueue没有区别。

在拿到任务后,会执行任务,也就是执行任务的run方法

java 复制代码
//执行任务	
public void run() {
           //判断线程池状态是不是running,如果不是running,且shutdown情况不允许执行,或者是stop状态
            if (!canRunInCurrentRunState(this))
			//取消任务
                cancel(false);
            else if (!isPeriodic())
			//当前任务是一次性执行,执行run方法执行完没了
                super.run();
            else if (super.runAndReset()) {
			//必然是周期执行
                setNextRunTime();
                reExecutePeriodic(outerTask);
            }
        }

四、scheduleAtFixedRate和scheduleWithFixedDelay分析

在执行方法的初期,封装任务时:

  • At会将period设置为正数,代表固定周期执行
  • With会将periods设置为负数,代表在执行任务完毕后,再计算下次执行的时间
java 复制代码
//固定周期执行任务,如果任务的执行时间超过周期,任务执行完,立即执行下一次任务		
 public ScheduledFuture<?> scheduleAtFixedRate(Runnable command,
                                               //第一次执行时间
                                                  long initialDelay,
												   //周期执行时间
                                                  long period,
                                                  TimeUnit unit) {
        if (command == null || unit == null)
            throw new NullPointerException();
			//健壮性判断
        if (period <= 0L)
            throw new IllegalArgumentException();
        ScheduledFutureTask<Void> sft =
            new ScheduledFutureTask<Void>(command,
                                          null,
                                          triggerTime(initialDelay, unit),
                                          unit.toNanos(period),
                                          sequencer.getAndIncrement());
			//扩展							  
        RunnableScheduledFuture<Void> t = decorateTask(command, sft);
		//将任务设置给outerTask属性,方便后期重新扔到延迟队列
        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 <= 0L)
            throw new IllegalArgumentException();
        ScheduledFutureTask<Void> sft =
            new ScheduledFutureTask<Void>(command,
                                          null,
                                          triggerTime(initialDelay, unit),
                                          -unit.toNanos(delay),
                                          sequencer.getAndIncrement());
        RunnableScheduledFuture<Void> t = decorateTask(command, sft);
        sft.outerTask = t;
        delayedExecute(t);
        return t;
    }

最终两个方法都会调用delayedExecute方法去将任务扔到阻塞队列,并尝试是否需要构建工作线程,从而执行任务。

工作线程会监听延迟队列,拿到任务后会调用任务的run方法。

java 复制代码
//执行任务	
public void run() {
            //查看At和with可以确定任务是周期执行
           //判断线程池状态是不是running,如果不是running,且shutdown情况不允许执行,或者是stop状态
            if (!canRunInCurrentRunState(this))
			//取消任务
                cancel(false);
            else if (!isPeriodic())
			//当前任务是一次性执行,执行run方法执行完没了
                super.run();
			//到这,先执行任务	
            else if (super.runAndReset()) {
			//设置下一次任务的运行时间
                setNextRunTime();
                reExecutePeriodic(outerTask);
            }
        }
	//计算任务下次执行时间,time 是任务执行的时间,这里是time的上次执行时间
private void setNextRunTime() {
       //拿到当前任务的period
            long p = period;
			//period>0:At
            if (p > 0)
			//At:直接拿上次执行的时间,添加上周期时间,来决定下次执行的时间
                time += p;
            else
			//period<0,with: 任务执行完,拿当前系统时间+延迟时间
                time = triggerTime(-p);
        }
	
//重新将任务扔到延迟队列中	
 void reExecutePeriodic(RunnableScheduledFuture<?> task) {
    //线程池状态的判断
        if (canRunInCurrentRunState(task)) {
		   //将任务扔到了延迟队列中
            super.getQueue().add(task);
			//扔到延迟队列后,再次判断线程池状态,是否需要取消任务
            if (canRunInCurrentRunState(task) || !remove(task)) {
			//判断是否需要创建线程
                ensurePrestart();
                return;
            }
        }
        task.cancel(false);
    }

异步、同步、阻塞、非阻塞。

同步:做了同步操作后,被调用者不会主动通知我结果,我需要主动查看结果。

异步:ajax,做了异步操作后,被调用这会主动通知我结果是什么。

阻塞:阻塞操作就是调用功能后,不能离开。

非阻塞:调用功能后,可以离开做其他事情。

同步阻塞:烧水功能时,不能离开,要一直盯着,并且烧水功能完后不会主动通知我

同步非阻塞:烧水功能时,可以离开,但是烧水功能完成后不会通知我

异步阻塞:烧水时,我不能做其他事情,但水烧开后会主动通知我.(这个操作没有)

异步非阻塞:执行烧水时,我可以做其他事情。并且水烧开后他可以主动通知我

http请求-请求一个接口查询结果,同步阻塞

Callable - 调用一个线程执行任务,有返回结果,我需要通过isDone查询或者get方法查询结果。这种就是同步非阻塞。

compatableFutrue-异步非阻塞

相关推荐
麦聪聊数据1 小时前
Web 原生架构如何重塑企业级数据库协作流?
数据库·sql·低代码·架构
未来之窗软件服务1 小时前
数据库优化提速(四)新加坡房产系统开发数据库表结构—仙盟创梦IDE
数据库·数据库优化·计算机软考
Fcy6481 小时前
Linux下 进程(一)(冯诺依曼体系、操作系统、进程基本概念与基本操作)
linux·运维·服务器·进程
袁袁袁袁满1 小时前
Linux怎么查看最新下载的文件
linux·运维·服务器
代码游侠2 小时前
学习笔记——设备树基础
linux·运维·开发语言·单片机·算法
主机哥哥2 小时前
阿里云OpenClaw部署全攻略,五种方案助你快速部署!
服务器·阿里云·负载均衡
Harvey9032 小时前
通过 Helm 部署 Nginx 应用的完整标准化步骤
linux·运维·nginx·k8s
Goat恶霸詹姆斯2 小时前
mysql常用语句
数据库·mysql·oracle
大模型玩家七七3 小时前
梯度累积真的省显存吗?它换走的是什么成本
java·javascript·数据库·人工智能·深度学习
曾经的三心草3 小时前
redis-9-哨兵
数据库·redis·bootstrap