JAVA并发包(十五):ScheduledThreadPoolExecutor

通过之前对线程池和延迟队列的研究,我们现在来学习ScheduleThreadPoolExecutor(简称STPE)就比较轻松了。可以把STPE理解为这样一个线程池,它里面的任务可以延迟一次性执行(叫做定时任务),也可以每隔一个周期重复执行(周期任务)。

STPE继承了ThreadPoolExecutor类,所以它的线程池运作原理就是ThreadPoolExecutor那一套,只是任务队列用了延迟队列,这样就具有了延迟定时的功能。另外它的周期任务的实现是这样的,每次执行周期任务之后,会重新生成一个延迟任务到线程池中,线程池的任务数量就是可以保持不变,也就能让任务持续周期执行。

下面我们通过源码来佐证我以上的阐述。

一、基本代码机构

从以下代码中可以看到,STPE是继承于ThreadPoolExecutor,实现的ScheduleExecutorService是一个具有周期方法的接口。另外,可以看到STPE内部维护了一个延迟队列内部类,其就是STPE的任务队列,用来实现延迟功能的。

public class ScheduledThreadPoolExecutor
        extends ThreadPoolExecutor
        implements ScheduledExecutorService {
	 /** 当线程池shutdown后是否继续执行存在周期任务 */
    private volatile boolean continueExistingPeriodicTasksAfterShutdown;

    /** 当线程池shutdown后是否继续执行存在的非周期任务(延迟任务) */
    private volatile boolean executeExistingDelayedTasksAfterShutdown = true;

    /**
     * True if ScheduledFutureTask.cancel should remove from queue
     */
    private volatile boolean removeOnCancel = false;

    /** 任务计数器,可以保证先进先出 */
    private static final AtomicLong sequencer = new AtomicLong();

    /**
     * Returns current nanosecond time.
     */
    final long now() {
        return System.nanoTime();
    }

	/** 调度的任务,这里省略 */
    private class ScheduledFutureTask<V>
            extends FutureTask<V> implements RunnableScheduledFuture<V> {}
    
    /** 内部实现的延迟队列,逻辑跟前面章节介绍的类似,省略 */   
	static class DelayedWorkQueue extends AbstractQueue<Runnable>
        implements BlockingQueue<Runnable> {}

	/** 构造器方法 */
	public ScheduledThreadPoolExecutor(int corePoolSize) {
		// 这里调用父类ThreadPoolExecutor的构造器,可以看到传了延迟队列DelayedWorkQueue
        super(corePoolSize, Integer.MAX_VALUE, 0, NANOSECONDS,
              new DelayedWorkQueue());
    }

}

二、延迟任务

延迟任务方法是schedule() ,它不会周期执行,只是在延迟一定时间后执行一次。下面是比较核心的几个方法,它可能不是在同一个类中,如果要看具体的代码位置建议打开jdk源码一起阅读。

		public ScheduledFuture<?> schedule(Runnable command,
	                                       long delay,
	                                       TimeUnit unit) {
	        if (command == null || unit == null)
	            throw new NullPointerException();
	        // 任务对象,这个对象我们比较关心的是run和compareTo方法,后面会讲解
	        // decorateTask就是一个装饰器方法,这里它直接返回了ScheduleFutureTask对象,triggerTime方法会对延迟时间做边界控制,防止溢出
	        RunnableScheduledFuture<?> t = decorateTask(command,
	            new ScheduledFutureTask<Void>(command, null,
	                                          triggerTime(delay, unit)));
	     	// 这里是提交任务,没太多的逻辑                                          
	        delayedExecute(t);
	        return t;
	    }
	
		private long triggerTime(long delay, TimeUnit unit) {
	        return triggerTime(unit.toNanos((delay < 0) ? 0 : delay));
	    }
	
		// 对延迟时间做校验,防止溢出。如果delay大于Long.MAX_VALUE >> 1,则做溢出的校验
		long triggerTime(long delay) {
	        return now() +
	            ((delay < (Long.MAX_VALUE >> 1)) ? delay : overflowFree(delay));
	    }
	
		/** 对大的延迟时间做溢出校验 */
		private long overflowFree(long delay) {
	        Delayed head = (Delayed) super.getQueue().peek();
	        if (head != null) {
	            long headDelay = head.getDelay(NANOSECONDS);
	            /* 这里考虑到一种情况,当入队列时,头节点调用getDelay方法小于0,
	            并且delay - headDelay 小于0的话,那么compareTo方法时,
	            可能会生产溢出。具体可以看下一个方法compareTo的
	            long diff = time - x.time; 这一行代码*/
	            if (headDelay < 0 && (delay - headDelay < 0))
	                delay = Long.MAX_VALUE + headDelay;
	        }
	        return delay;
	    }
    
   	 	/** 这个比较方法是为了构建最小堆的数据结构 */
		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;
        }
 	
 		/** 延迟方法的构造器中,period=0,说明不是周期任务 */
 		ScheduledFutureTask(Runnable r, V result, long ns) {
            super(r, result);
            this.time = ns;
            this.period = 0;
            this.sequenceNumber = sequencer.getAndIncrement();
        }

		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);
            }
        }
		
		/** 判断是否为周期任务 */
		public boolean isPeriodic() {
            return period != 0;
        }

		/** 提交延迟任务 */
		private void delayedExecute(RunnableScheduledFuture<?> task) {
	        if (isShutdown())
	            reject(task);
	        else {
	        	// 任务添加到任务队列,不是直接执行,而是由工作线程之后去队列里获取任务执行
	            super.getQueue().add(task);
	            if (isShutdown() &&
	                !canRunInCurrentRunState(task.isPeriodic()) &&
	                remove(task))
	                task.cancel(false);
	            else
	                ensurePrestart();
	        }
	    }

		/** 判断是否需要增加一个执行任务的线程 */
		void ensurePrestart() {
	        int wc = workerCountOf(ctl.get());
	        if (wc < corePoolSize)
	            addWorker(null, true);
	        else if (wc == 0)
	            addWorker(null, false);
	    }

三、周期任务

周期任务的逻辑主要看ScheduledFutureTask类的run方法,最后一个分支就是周期任务。它首先会执行一次任务,然后再往任务队列添加下一次执行的任务。

// run方法的分支,runAndReset方法会执行一次任务,然后设置下一次任务的触发时间,最后把新的任务加到任务队列中,等待调度执行
else if (ScheduledFutureTask.super.runAndReset()) {
     setNextRunTime();
     reExecutePeriodic(outerTask);
}

/** 这里就是设置下一次执行的时间,直接加上周期的时间*/
private void setNextRunTime() {
     long p = period;
     if (p > 0)
         time += p;
     else
         time = triggerTime(-p);
}

/** 提交下一次执行的任务到队列中 */
void reExecutePeriodic(RunnableScheduledFuture<?> task) {
     if (canRunInCurrentRunState(true)) {
         super.getQueue().add(task);
            if (!canRunInCurrentRunState(true) && remove(task))
                task.cancel(false);
            else
                ensurePrestart();
     }
}

四、总结

ScheduledThreadPoolExecutor线程池原理大概可以为总结如下两点:

  1. 延迟功能依赖于延迟队列的最小堆数据结构,延迟执行的实现是基于定时的时间跟当前时间做比较。
  2. 周期任务是执行当前任务后,再往任务队列添加一个定时任务。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值