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 小时前
Mysql
数据库·mysql
Bruce_Liuxiaowei1 小时前
⛏️ Windows 系统挖矿病毒排查与处置技术指南
运维·windows·网络安全
凯子坚持 c1 小时前
《openGauss向量数据库_助力企业RAG应用落地实践》
数据库
小熊officer1 小时前
mysql创建用户以及赋予权限
数据库·mysql
@游子1 小时前
SQL注入之文件读写(四)
android·数据库·sql
我科绝伦(Huanhuan Zhou)1 小时前
深入探索Oracle数据库空间管理与监控
数据库·oracle
2***63551 小时前
SQL常用语句(基础)大全
数据库·sql·oracle
好好研究1 小时前
MyBatis框架 - 逆向工程
java·数据库·mybatis