并发编程之定时任务&定时线程池原理解析( 六 )

代码的整体逻辑为:

  • 判断当前任务的state是否等于NEW , 如果不为NEW则说明任务或者已经执行过 , 或者已经被取消 , 直接返回;
  • 如果状态为NEW则接着会通过unsafe类把任务执行线程引用CAS的保存在runner字段中 , 如果保存失败 , 则直接返回;
  • 执行任务;如果任务执行发生异常 , 则调用setException()方法保存异常信息 。
FutureTask类的runAndReset方法方法的源码如下所示:
protected boolean runAndReset() { if (state != NEW ||  !UNSAFE.compareAndSwapObject(this, runnerOffset,          null, Thread.currentThread()))  return false; boolean ran = false; int s = state; try {  Callable<V> c = callable;  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); } return ran && s == NEW;}FutureTask类的runAndReset方法与run方法的逻辑基本相同 , 只是runAndReset方法会重置当前任务的执行状态 。
ScheduledThreadPoolExecutor类的reExecutePeriodic方法reExecutePeriodic重复执行任务方法 , 源代码如下所示:
void reExecutePeriodic(RunnableScheduledFuture<?> task) { //线程池当前状态下能够执行任务 if (canRunInCurrentRunState(true)) {  //与ThreadPoolExecutor不同 , 这里直接把任务加入延迟队列        super.getQueue().add(task);//使用用的DelayedWorkQueue  //线程池当前状态下不能执行任务 , 并且成功移除任务  if (!canRunInCurrentRunState(true) && remove(task))   //取消任务   task.cancel(false);  else   //这里是增加一个worker线程 , 避免提交的任务没有worker去执行            //原因就是该类没有像ThreadPoolExecutor一样 , woker满了才放入队列     ensurePrestart(); }}总体来说reExecutePeriodic方法的逻辑比较简单 , 需要注意的是:调用reExecutePeriodic方法的时候已经执行过一次任务 , 所以 , 并不会触发线程池的拒绝策略;传入reExecutePeriodic方法的任务一定是周期性的任务 。
DelayedWorkQueueScheduledThreadPoolExecutor之所以要自己实现阻塞的工作队列 , 是因为 ScheduleThreadPoolExecutor 要求的工作队列有些特殊 。
DelayedWorkQueue是一个基于堆的数据结构 , 类似于DelayQueue和PriorityQueue 。在执行定时任务的时候 , 每个任务的执行时间都不同 , 所以DelayedWorkQueue的工作就是按照执行时间的升序来排列 , 执行时间距离当前时间越近的任务在队列的前面(注意:这里的顺序并不是绝对的 , 堆中的排序只保证了子节点的下次执行时间要比父节点的下次执行时间要大 , 而叶子节点之间并不一定是顺序的) 。
堆结构如下图:
并发编程之定时任务&定时线程池原理解析

文章插图
 
可见 , DelayedWorkQueue是一个基于最小堆结构的队列 。堆结构可以使用数组表示 , 可以转换成如下的数组:
并发编程之定时任务&定时线程池原理解析

文章插图
 
在这种结构中 , 可以发现有如下特性: 假设“第一个元素” 在数组中的索引为 0 的话 , 则父结点和子结点的位置关系如下: