Java并发编程(十九):ScheduledThreadPoolExecutor总结与源码分析

ScheduledThreadPoolExecutor用于处理延时和定时任务,它扩展了ThreadPoolExecutor并实现了ScheduledExecutorService接口。文章详细介绍了ScheduledThreadPoolExecutor的使用方法,包括schedule、scheduleAtFixedRate和scheduleWithFixedDelay等,以及源码分析,特别是DelayedWorkQueue的工作原理和Leader-Follower线程模型。同时,对比了ScheduledThreadPoolExecutor和Timer的区别,强调ScheduledThreadPoolExecutor在并发处理和任务调度上的优势。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

前言

  ScheduledThreadPoolExecutor主要用于处理延时任务或者定时任务,在平时的工作场景中也有被广泛应用,而我们有了前文关于ThreadPoolExecutor源码的深入理解,这里理解ScheduledThreadPoolExecutor便会顺畅不少。

使用

  ScheduledThreadPoolExecutor的使用还是比较简单,总体来说主要有三种方式提交任务:

  • schedule:延时执行一个任务
schedule(runnable, 3, TimeUnit.SECONDS);

  表示延时3秒执行runnable任务, 同时schedule还支持提交一个Callable任务,通过返回的ScheduledFuture可以在后续的流程中获取任务的执行结果。

  • scheduleAtFixedRate:相对于schedule方法,多了一个period参数,表示每隔多久执行一次任务,可以使用它实现周期调度任务的目的。
scheduleAtFixedRate(runnable, 1, 2, TimeUnit.SECONDS);

  表示延时1秒开始执行runnable,并且并且之后每隔2秒执行一次runnable。要注意,如果任务的执行时间超过了间隔时间(period),那么在任务执行完成后会立即执行下一次runnable,也就是说严格按照period间隔时间计算,只是到了period指定的间隔,由于任务还在执行,所以等待任务执行完成后会立即调度下一次执行。在一个任务执行完成后才会插入下一次需要执行的任务,所以不会存在任务堆积的情况。

  • scheduleWithFixedDelay:用法和scheduleAtFixedRate类似,相对于schedule方法多了一个delay参数,也用于提交周期任务。和scheduleAtFixedRate的区别是,如果任务执行时间超过了delay参数,那么任务执行完后还是会再等待delay指定的时间才运行下一次任务,也就是说delay从任务结束的时间开始算。
scheduleWithFixedDelay(runnable, 3, 2, TimeUnit.SECONDS);

注:同时也支持execute和submit方法提交任务,实现就是通过schedule提交一个延时为0的任务。

源码分析

在这里插入图片描述
  从类继承关系图中可以看出,ScheduledThreadPoolExecutor继承了ThreadPoolExecutor,所以它具备ThreadPoolExecutor的能力,同时实现了ScheduledExecutorService接口,该接口定义了4个方法:
在这里插入图片描述
  这几个接口使得ScheduledThreadPoolExecutor具备了延时执行和定时调度的能力。
  首先来看构造方法,从源码看来,ScheduledThreadPoolExecutor提供了四个构造方法:

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

  构造器主要就是三个参数:

  • corePoolSize:线程池大小
  • threadFactory:线程工厂
  • handler:拒绝策略

  ScheduledThreadPoolExecutor是ThreadPoolExecutor的子类,但是可以从构造函数中看到,指定的maximumPoolSize都是Integer.MAX_VALUE,并且阻塞队列使用的是DelayedWorkQueue,keepAliveTime设置为0。从这些参数设置中我们就能发现,对于ScheduledThreadPoolExecutor来说,没有了核心线程池的概念,因为队列DelayedWorkQueue是一个无界队列,所以线程数不可能超过corePoolSize的大小。

schedule实现

public ScheduledFuture<?> schedule(Runnable command,
                                       long delay,
                                       TimeUnit unit) {
   
   
        if (command == null || unit == null)
            throw new NullPointerException();
        //计算任务的触发时间,然后将任务command包装为一个ScheduledFutureTask,然后调用decorateTask方法进行进一步包装
        //decorateTask方法在这里没有做任何包装操作,如果需要可以继承然后自己实现
        RunnableScheduledFuture<?> t = decorateTask(command, new ScheduledFutureTask<Void>(command, null, triggerTime(delay, unit)));
		//提交任务
        delayedExecute(t);
        return t;
    }

  调用方法很简单,首先会根据delay和unit计算任务的触发时间,计算逻辑就是当前时间加上延时时间,这个代码这里就不贴了。然后将触发时间和任务包装成一个ScheduledFutureTask,再然后会调用decorateTask进行进一步包装,包装成一个RunnableScheduledFuture,这个decorateTask方法在ScheduledThreadPoolExecutor中没有做什么其它操作,直接返回的是上一步创建的ScheduledFutureTask,子类可以重写该方法对任务进行进一步的包装。

注:ScheduledFutureTask是ScheduledThreadPoolExecutor的子类

在这里插入图片描述
  从ScheduledFutureTask的继承关系中可以看出,它实现了Comparable接口,具备比较大小的功能;实现了Future接口,具备获取执行结果的功能;实现了Runnable,可以作为任务被运行。
  但是Runnable是不具备返回值的,这里怎么返回的ScheduledFutureTask呢?可以来看看ScheduledFutureTask关于Runnable的构造函数:

	ScheduledFutureTask(Runnable r, V result, long ns) {
   
   
			//result表示任务的返回值
            super(r, result);
            this.time = ns;
            this.period = 0;
            this.sequenceNumber = sequencer.getAndIncrement();
        }

  该构造函数需要一个任务返回值,在前面schedule(runnable)的方法中传入的是null,表示一个空的返回,然后到父类FutureTask中看看:

	public FutureTask(Runnable runnable, V result) {
   
   
		//将runnable包装横一个Callable
        this.callable = Executors.callable(runnable, result);
        this.state = NEW;       // ensure visibility of callable
    }

  在父类FutureTask中,Runnable被转换成了Callable,这是通过Executors.callable实现的:

	public static <T> Callable<T> callable(Runnable task, T result) {
   
   
        if (task == null)
            throw new NullPointerException();
        return new RunnableAdapter<T>(task, result);
    }
    //适配器
    
评论 12
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值