Java并发编程:ScheduledThreadPoolExecutor源码解析

1 ScheduledThreadPoolExecutor类图

ScheduledThreadPoolExecutor类图如下
ScheduledThreadPoolExecutor类图
ScheduledThreadPoolExecutor继承了ThreadPoolExecutor并实现了ScheduledExecutorService接口。线程池队列是DelayedWorkQueue,是一个延迟队列。
ScheduledFutureTask是具有返回值的任务,继承自FutureTask。FutureTask的内部有一个变量state用来表示任务状态,所有状态为:

 	private volatile int state;
 	
  	/**
     * 	初始状态
     */
    private static final int NEW          = 0;
    /**
     * 	执行中的状态
     */
    private static final int COMPLETING   = 1;
    
    /**
     *  正常运行结束状态
     */
    private static final int NORMAL       = 2;
    
    /**
     * 	运行中异常
     */
    private static final int EXCEPTIONAL  = 3;

	/**
     * 	任务被取消
     */
    private static final int CANCELLED    = 4;

	/**
     * 	任务正在被中断
     */
    private static final int INTERRUPTING = 5;

	/**
     * 	任务已经被中断
     */
    private static final int INTERRUPTED  = 6;

可能的任务状态转换路径为
NEW → COMPLETING → NORMAL
NEW → COMPLETING → EXCEPTIONAL
NEW → CANCELED
NEW → INTERRUPTING→ INTERRUPTED
ScheduledFutureTask内部还有一个变量period用来表示任务的类型,任务类型如下:

  • period=0,说明当前任务是一次性的,执行完毕就退出
  • period为负数,说明当前任务是fixed-delay任务,是固定延迟的定时可重复执行任务
  • period为正数,说明当前任务是fixed-rate任务,是固定频率的定时可重复执行任务

下面是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);
 }

由构造函数可知线程队列是DelayedWorkQueue。

2 原理剖析

2.1 public ScheduledFuture<?> schedule(Runnable command, long delay, TimeUnit unit)方法

该方法的作用是提交一个延迟执行的任务,任务从提交时间起延迟单位为unit的delay时间后开始执行。提交的任务不是周期性任务,任务只会执行一次。

 public ScheduledFuture<?> schedule(Runnable command,
                                       long delay,
                                       TimeUnit unit) {
        // 参数检验
        if (command == null || unit == null)
            throw new NullPointerException();
        // 任务转换,可以继承,重写decorateTask,decorateTask原方法什么都没做
        RunnableScheduledFuture<?> t = decorateTask(command,
            new ScheduledFutureTask<Void>(command, null,
                                          triggerTime(delay, unit)));
        // 添加任务到延迟队列
        delayedExecute(t);
        return t;
}

着重说一下上述代码中的任务转换,decorateTask的作用是把提交的command任务转换为
ScheduledFutureTask。ScheduledFutureTask实现了long getDelay(TimeUnit unit)和
int compateTo(Delayed other)方法。
triggerTime(delay, unit)方法将延迟时间转换为绝对时间,也就是把当前时间的纳秒加上延迟纳秒后的long型值。
ScheduledFutureTask的构造函数如下

ScheduledFutureTask(Runnable r, V result, long ns) {
		// 调用父类的构造方法
       super(r, result);
       this.time = ns;
       // period为0,说明为一次性任务
       this.period = 0;
       // 序列号
       this.sequenceNumber = sequencer.getAndIncrement();
}

在构造函数内部首先调用父类FutureTask的构造函数,代码如下:

public FutureTask(Runnable runnable, V result) {
	  // 通过适配器把runnable转换为callable
      this.callable = Executors.callable(runnable, result);
      // 设置当前任务状态为NEW
      this.state = NEW;       // ensure visibility of callable
}

在FutureTask构造函数里面,把任务类型转换为Callable类型后,被保存到了变量this.callable里面,并设置任务状态为NEW。
ScheduledFutureTask构造函数内部设置time为绝对时间,long getDelay(TimeUnit unit)如下

 public long getDelay(TimeUnit unit) {
 		// 计算过期时间,装饰后的时间-当前时间,即将过期剩余时间
        return unit.convert(time - now(), NANOSECONDS);
 }

campareTo(Delayed other)方法的代码如下:

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;
}

compareTo的作用是加入元素到延迟队列后,在内部建立或者调整堆时会使用该元素的compareTo方法与队列里面其他元素进行比较,让最快过期的元素放到队首,所以无论什么时候向队列里面添加元素,队首的元素都是最快过期的元素。
将任务添加到延迟队列,delayedExecute的代码如下

 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();
       }
}

这里解释一下,为什么要检查两次线程池是否被关闭了?
第一次检查线程池是否被关闭了,是因为需要关闭就执行线程池的拒绝策略,没有关闭的话,就将任务添加到延迟队列。
第二次检查是任务添加完成后,如果已经关闭,那么从延迟队列里面删除刚才添加的任务,由于此时有可能线程池中的线程已经从延迟队列里面获取了该任务,也就是该任务在执行了,所有还需要条用任务的cancle方法取消任务。

void ensurePrestart() {
    int wc = workerCountOf(ctl.get());
    // 线程个数小于核心线程数,就增加一个核心线程数
    if (wc < corePoolSize)
         addWorker(null, true);
    // 如果初始化corePoolSize==0,则添加一个线程     
    else if (wc == 0)
         addWorker(null, false);
}

由源码可以看出,ensurePrestart()就是添加线程,确保至少有一个线程在处理任务。
前面分析了如何向延迟队列添加任务,下面我们来看线程池的线程获取并执行任务。
类比一下,ThreadPoolExecutor里面,具体执行任务的线程是worker线程,worker线程
调用具体任务的run方法来执行,由于这里的任务是ScheduledFutureTask,看看ScheduledFutureTask的run方法

 /**
   * Overrides FutureTask version so as to reset/requeue if periodic.
   */
  public void run() {
  	   // 判断是否执行一次
       boolean periodic = isPeriodic();
       // 取消任务
       if (!canRunInCurrentRunState(periodic))
           cancel(false);
       // 只执行一次,调用schedule方法的时候
       else if (!periodic)
           ScheduledFutureTask.super.run();
       // 定时执行
       else if (ScheduledFutureTask.super.runAndReset()) {
           // 设置时间
           setNextRunTime();
           // 重新加入该任务到delay队列
           reExecutePeriodic(outerTask);
       }
   }

   /**
	 * 判断执行一次还是重复执行
	 */
   public boolean isPeriodic() {
          return period != 0;
    }

	/**
   	  * 判断当前任务是否应该被取消 executeExistingDelayedTasksAfterShutdown(默认true)
   	  * executeExistingDelayedTasksAfterShutdown默认为true,表示当其他线程调用shutdown
   	  * 命令关闭了线程池后,当前任务还是要执行,如果为false,则当前任务要被取消
	  * 执行一次的时候,periodic为false
  	  */
	boolean canRunInCurrentRunState(boolean periodic) {
        return isRunningOrShutdown(periodic ?
                                   continueExistingPeriodicTasksAfterShutdown :
                                   executeExistingDelayedTasksAfterShutdown);
    }

	private void setNextRunTime() {
         long p = period;
         if (p > 0) // fixed-rate类型
             time += p;
         else // fixed-delay类型
             time = triggerTime(-p);
    }
 	/**
     * 是ThreadPoolExecutor中的方法
     * @param shutdownOK 表示线程池能在SHUTDOWN状态下运行
     */
    final boolean isRunningOrShutdown(boolean shutdownOK) {
        // 线程状态
        int rs = runStateOf(ctl.get());
        // 当线程是RUNNING状态,或者是SHUTDOWN状态且shutdownOK也为true
        return rs == RUNNING || (rs == SHUTDOWN && shutdownOK);
    }

如果periodic为false,则调用父类FutureTask的run方法执行具体任务

public void run() {
     // a
	 // 如果任务状态不是NEW则直接返回
	 // 如果当前状态是NEW,但是使用CAS设置当前任务的持有者为当前线程失败也返回
     if (state != NEW ||
          !UNSAFE.compareAndSwapObject(this, runnerOffset,
                                       null, Thread.currentThread()))
          return;
     // 调用callable的call()执行具体任务
      try {
          Callable<V> c = callable;
          // 再次判断任务状态是否为NEW,是为了避免执行a代码块之后,其他线程修改了任务状态
          // 比如取消了任务
          if (c != null && state == NEW) {
              V result;
              boolean ran;
              try {
                  result = c.call();
                  ran = true;
              } catch (Throwable ex) {
                  result = null;
                  ran = false;
                  // 发生异常,运行失败
                  setException(ex);
              }
              if (ran)
                  // 运行成功,修改任务状态
                  set(result);
          }
      } finally {
          // runner must be non-null until state is settled to
          // prevent concurrent calls to run()
          runner = null;
          // state must be re-read after nulling runner to prevent
          // leaked interrupts
          int s = state;
          if (s >= INTERRUPTING)
              handlePossibleCancellationInterrupt(s);
      }
}

protected void set(V v) {
	 // 如果当前任务状态为NEW,则设置为COMPLETING
     if (UNSAFE.compareAndSwapInt(this, stateOffset, NEW, COMPLETING)) {
          outcome = v;
          // 设置当前任务状态为NORMAL,也就是任务正常结束了
          UNSAFE.putOrderedInt(this, stateOffset, NORMAL); // final state
          finishCompletion();
      }
}

protected void setException(Throwable t) {
 	// 如果当前任务状态为NEW,则设置为COMPLETING
     if (UNSAFE.compareAndSwapInt(this, stateOffset, NEW, COMPLETING)) {
          outcome = t;
          // 设置当前任务状态为EXCEPTIONAL,也就是任务非正常结束了
          UNSAFE.putOrderedInt(this, stateOffset, EXCEPTIONAL); // final state
          finishCompletion();
      }
}

思考题,在什么时候多个线程会同时执行CAS将当前任务的状态从NEW转换为COMPLETING?

2.2 public ScheduledFuture<?> scheduleWithFixedDelay(Runnable command,long initialDelay, long delay,TimeUnit unit)方法

该方法的作用是,当任务执行完毕后,让其延迟固定时间后再次运行(fixed-delay任务)。
其中initialDelay表示提交任务后延迟多少时间开始执行command,delay表示当任务执行完毕后延长多少时间后再次运行command任务,unit是initialDelay和delay的时间单位。任务会一直重复运行直到任务中抛出了异常,别取消了,或者关闭了线程池。

 public ScheduledFuture<?> scheduleWithFixedDelay(Runnable command,
                                                     long initialDelay,
                                                     long delay,
                                                     TimeUnit unit) {
      // 参数检验
      if (command == null || unit == null)
           throw new NullPointerException();
       if (delay <= 0)
           throw new IllegalArgumentException();
       // 任务转换     
       ScheduledFutureTask<Void> sft =
           new ScheduledFutureTask<Void>(command,
                                         null,
                                         triggerTime(initialDelay, unit),
                                         unit.toNanos(-delay));
       RunnableScheduledFuture<Void> t = decorateTask(command, sft);
       // outerTask作用就是下一次定时执行的任务,在reExecutePeriodic方法中需要
       sft.outerTask = t;
       // 添加任务到延迟队列
       delayedExecute(t);
       return t;
}

需要看的是,这里传递给ScheduledFutureTask的period变量的值为-delay,delay<0说明该任务为可重复执行的任务。
将任务添加到延迟队列后,线程池线程会从队列里面获取任务,然后调用ScheduledFutureTask的run()执行。
ThreadPoolExecutor真正的线程工作类是内部类Worker,线程start启动的时候,会运行Worker的run方法,Worker的run()会循环从队列获取任务。
下面解析一下DelayedWorkQueue的take()方法

public RunnableScheduledFuture<?> take() throws InterruptedException {
      	final ReentrantLock lock = this.lock;
        // 可中断的锁,优先响应中断
        lock.lockInterruptibly();
        try {
            for (;;) {
            	// 获取队首元素
                RunnableScheduledFuture<?> first = queue[0];
                // 如果first为空,则当前线程进入条件队列,等待
                if (first == null)
                    available.await();
                else {
                	// 如果不为空,判断过期时间
                    long delay = first.getDelay(NANOSECONDS);
                    // 过期时间<=0,说明已经过期,那么直接出队列
                    if (delay <= 0)
                        return finishPoll(first);
                    // 在等待期间不能保留引用    
                    first = null; // don't retain ref while waiting
                    // 如果leader线程不为空,则当前线程进入条件队列,等待
                    if (leader != null)
                        available.await();
                    else {
                    	// 如果leader线程为空,则把当前线程赋值给leader
                        Thread thisThread = Thread.currentThread();
                        leader = thisThread;
                        try {
                        	// 当前线程进入条件队列,等待delay时间,在等待时间内释放锁
                        	// 等待时间结束后,会重新获取锁
                            available.awaitNanos(delay);
                        } finally {
                        	// 重置leader线程为null,重新进入循环体,这时就会发现队首资源已经过期
                            if (leader == thisThread)
                                leader = null;
                        }
                    }
                }
            }
        } finally {
            // leader为null,并且移除队首元素之后的新的队首元素不为空
            if (leader == null && queue[0] != null)
                // 激活条件队列里面的等待线程
                available.signal();
            // 释放资源
            lock.unlock();
        }
}


private RunnableScheduledFuture<?> finishPoll(RunnableScheduledFuture<?> f) {
           int s = --size;
           RunnableScheduledFuture<?> x = queue[s];
           queue[s] = null;
           if (s != 0)
               // 调整堆的大小
               siftDown(0, x);
           // 移出队首元素    
           setIndex(f, -1);
           return f;
}

取任务的流程如上,下面说一下添加任务的源码分析

public boolean offer(Runnable x) {
       // 参数检验
       if (x == null)
            throw new NullPointerException();
       // 类型转换     
       RunnableScheduledFuture<?> e = (RunnableScheduledFuture<?>)x;
       final ReentrantLock lock = this.lock;
       // 获取独占锁
       lock.lock();
       try {
            int i = size;
            // 如果队列长度不够,进行扩容
            if (i >= queue.length)
                grow();
            size = i + 1;
            // 如果i=0,直接添加到队首
            if (i == 0) {
                queue[0] = e;
                setIndex(e, 0);
            } else {
            	// 否则调整堆的结构,按照时间调整
                siftUp(i, e);
            }
            // 如果传入的元素正好是队首元素
            if (queue[0] == e) {
                // 设置leader线程为null
                leader = null;
                // 唤醒条件队列里等待的线程(一个)
                available.signal();
            }
       } finally {
       		// 释放资源
            lock.unlock();
       }
       // 添加任务成功,返回true
       return true;
}

细心的读者可能发现take()和offer()里面都出现了leader变量,简单说一下leader变量。
leader变量是基于Leader-Follower模式的变体,用于尽量减少不必要的线程等待。当一个线程调用队列的take()变为leader线程后,他会调用条件变量available.awaitNanos(delay)等待delay时间,但是其他线程(follower线程)则会调用avaliable.await()进行无限等待。leader线程延迟时间过期后,获取任务,退出take(),并通过调用avaliable.signal()方法唤醒一个follower线程,被唤醒的follower线程被选举为新的leader线程。
言归正传,在讲一次性任务schedule()的时候,讲到了ScheduledFutureTask的run(),如果是周期性的任务会执行runAndReset(),不清楚的读者可以往上看看。

protected boolean runAndReset() {
     // b
     // 如果任务状态不是NEW则直接返回
	 // 如果当前状态是NEW,但是使用CAS设置当前任务的持有者为当前线程失败也返回
     if (state != NEW ||
          !UNSAFE.compareAndSwapObject(this, runnerOffset,
                                       null, Thread.currentThread()))
          return false;
      // task执行成功的状态量    
      boolean ran = false;
      int s = state;
      try {
      	  // 调用callable的call()执行具体任务	
          Callable<V> c = callable;
          // 再次判断任务状态是否为NEW,是为了避免执行b代码块之后,其他线程修改了任务状态
          // 比如取消了任务
          if (c != null && s == NEW) {
              try {
                  c.call(); // don't set result
                  ran = true;
              } catch (Throwable ex) {
                  setException(ex);
              }
          }
      } finally {
          // runner must be non-null until state is settled to
          // prevent concurrent calls to run()
          runner = null;
          // state must be re-read after nulling runner to prevent
          // leaked interrupts
          s = state;
          if (s >= INTERRUPTING)
              handlePossibleCancellationInterrupt(s);
      }
      // 任务正常执行完,并且任务状态为NEW,返回truee
      return ran && s == NEW;
}

简单总结一下:
fixed-delay类型的任务,当添加一个任务到延迟队列后,会等待initialDelay时间,任务就会过期,过期的任务就会被队列移除,之后执行任务。执行完毕后,会重新设置任务的延迟时间,然后再把任务放入到延迟队列,循环执行。注意,如果一个任务在执行中出现了异常,那么这个任务就结束了,不影响其他任务的执行

2.3 public ScheduledFuture<?> scheduleWithFixedRate(Runnable command,long initialDelay, long delay,TimeUnit unit)方法

该方法相对起始时间以固定频率调用指定的任务(fixed-rate任务)。当把任务提交到线程池并延迟initialDelay时间(时间单位为unit)后开始执行command。然后从initialDelay+period时间点开始再次执行,而后在initialDelay+n*period时间点再次执行,循环往复,直到抛出了异常或者调用了任务的cancel方法取消了任务,或者关闭了线程池。

public ScheduledFuture<?> scheduleAtFixedRate(Runnable command,
                                                  long initialDelay,
                                                  long period,
                                                  TimeUnit unit) {
      // 参数检验
      if (command == null || unit == null)
          throw new NullPointerException();   
      if (period <= 0)
          throw new IllegalArgumentException();
      // 装饰任务,此时period>0    
      ScheduledFutureTask<Void> sft =
          new ScheduledFutureTask<Void>(command,
                                        null,
                                        triggerTime(initialDelay, unit),
                                        unit.toNanos(period));
      RunnableScheduledFuture<Void> t = decorateTask(command, sft);
      sft.outerTask = t;
      delayedExecute(t);
      return t;
}

当前任务执行完毕后,调用setNextRunTime设置下次执行的时间,执行的是time+=period,不再是time=triggerTime(-p)

3 归纳总结

简单论述了ScheduledThreadPoolExecutor的实现原理,内部使用DelayedWorkQueue来存放具体任务,任务类型用period的值来区分。
任务分为三种:第一种是一次性执行完毕的任务;第二种是fixed-delay,保证同一个任务在多次执行之间间隔固定时间。第三种是fixed-rate,抱枕任务按照固定的频率执行任务。
还有个问题,需要读者注意一下,由于DelayedQueue是无界队列,所以SecheduledThreadPoolExecutor只会建立corePoolSize个核心线程,所有的线程都是核心线程。
ScheduledThreadPoolExecutor示意图

发布了16 篇原创文章 · 获赞 5 · 访问量 3295

猜你喜欢

转载自blog.csdn.net/qq_32573109/article/details/103042216