ScheduledThreadPoolExecutor

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

    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; // 任务已经被中断

       任务状态转换路径为

NEN-> COMPLETING-> NORMAL//初始状态->执行中ー>正常结東
NEN-> COMPILETING-> EXCEPTIONAL//初始状态->执行中ー>执行异常
NEN-> CANCELLED//初始状态一>任务取消
NEN-> INTERRUPTING-> INTERRUPTED//初始状态->被中断中->被中断

      ScheduledFutureTask内部还有一个变量period用来表示任务的类型,任务类型如下

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

     构造函数

// 使用改造后的DelayQueue
    public ScheduledThreadPoolExecutor(int corePoolSize) {
        // 调用父类ThreadPoolExecutor的构造函数
        super(corePoolSize, Integer.MAX_VALUE, 0, NANOSECONDS,
              new DelayedWorkQueue());
    }

    schedule(Runnable command, long delay,TimeUnit unit)

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

    public ScheduledFuture<?> schedule(Runnable command,
                                       long delay,
                                       TimeUnit unit) {
        //  1 参数校验                               
        if (command == null || unit == null)
            throw new NullPointerException();
        //  2 任务转换 
        RunnableScheduledFuture<?> t = decorateTask(command,
            new ScheduledFutureTask<Void>(command, null,
                                          triggerTime(delay, unit)));
        // 3 添加任务到延迟队列                                  
        delayedExecute(t);
        return t;
    }

      ScheduledFutureTask是具体放入延迟队列里面的东西。由于是延迟任务,所以ScheduledFutureTask实现了long getDelay(TimeUnit unit)和int compareTo(Delayed other)方法。triggerTime方法将延迟时间转换为绝对时间,也就是把当前时间的纳秒数加上延迟的纳秒数后的long型值。

  ScheduledFutureTask的构造函数

  /**
         * Creates a one-shot action with given nanoTime-based trigger time.
         */
        ScheduledFutureTask(Runnable r, V result, long ns) {
            // 调用父类FutureTask的构造函数
            super(r, result);
            this.time = ns;
            this.period = 0; // 0 说明是一次性任务
            this.sequenceNumber = sequencer.getAndIncrement();
        }

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

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

      FutureTask中的任务被转换为Callable类型后,被保存到了变量this.callable里面,并设置FutureTask的任务状态为NEW。

      在ScheduledFutureTask构造函数内部设置time为上面说的绝对时间。需要注意,这里period的值为0,这说明当前任务为一次性的任务,不是定时反复执行任务。其中long getDelay(TimeUnit unit)方法的代码如下(该方法用来计算当前任务还有多少时间就过期了)。

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

        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) {
        // 4 如果线程池拐臂了,则执行线程执行拒绝策略
        if (isShutdown())
            reject(task);
        else {
            // 5 添加任务到延迟队列
            super.getQueue().add(task);
            // 6 再次校验
            if (isShutdown() &&
                !canRunInCurrentRunState(task.isPeriodic()) &&
                remove(task))
                task.cancel(false);
            else
                // 7 确保至少一个线程在处理任务
                ensurePrestart();
        }
    }

  代码(4)首先判断当前线程池是否已经关闭了,如果已经关闭则执行线程池的拒绝策略 ,否则执行代码(5)将任务添加到延迟队列

  添加完毕后还要重新检查线程池是否被关闭了,如果已经关闭则从延迟队列里面删除刚才添加的任务,但是此时有可能线程池中的线程已经从任务队列里面移除了该任务,也就是该任务已经在执行了,所以还需要调用任务的cancle方法取消任务。
  如果代码(6)判断结果为false,则会执行代码(7)确保至少有一个线程在处理任务,即使核心线程数corePoolSize被设置为0

    /**
     * Same as prestartCoreThread except arranges that at least one
     * thread is started even if corePoolSize is 0.
     */
    void ensurePrestart() {
        int wc = workerCountOf(ctl.get());
        // 增加核心线程数
        if (wc < corePoolSize)
            addWorker(null, true);
        // 如果初始化corePoolSize=0,则也添加一个线程    
        else if (wc == 0)
            addWorker(null, false);
    }

      首先获取线程池中的线程个数,如果线程个数小于核心线程数则新增一个线程,否则如果当前线程数为0则新增一个线程。看看ScheduledFutureTask的run方法

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

       isPeriodic的作用是判断当前任务是一次性任务还是可重复执行的任务

 public boolean isPeriodic() {
            return period != 0;
        }

      内部是通过period的值来判断的,由于转换任务在创建ScheduledFutureTask时传递的period的值为0 ,所以这里isPeriodic返回false。

       判断当前任务是否应该被取消,canRunInCurrentRunState的代码如下

boolean canRunInCurrentRunState(boolean periodic) {
        return isRunningOrShutdown(periodic ?
                                   continueExistingPeriodicTasksAfterShutdown :
                                   executeExistingDelayedTasksAfterShutdown);
    }

  传递的periodic的值为false,isRunningOrShutdown的参数为executeExistingDelayedTasksAfterShutdown。executeExistingDelayedTasksAfterShutdown默认为true,表示当其他线程调用了shutdown命令关闭了线程池后,当前任务还是要执行,否则如果为false,则当前任务要被取消。

  由于periodic的值为false,所以执行代码(10)调用父类FutureTask的run方法具体执行任务。FutureTask的run方法的代码如下

  public void run() {
          // 12 
        if (state != NEW ||
            !UNSAFE.compareAndSwapObject(this, runnerOffset,
                                         null, Thread.currentThread()))
            return;
        // 13     
        try {
            Callable<V> c = callable;
            if (c != null && state == NEW) {
                V result;
                boolean ran;
                try {
                    result = c.call();
                    ran = true;
                } catch (Throwable ex) {
                    result = null;
                    ran = false;
                    // 13.1
                    setException(ex);
                }
                // 13.2
                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);
        }
    }

  代码(12)判断如果任务状态不是NEW则直接返回,或者如果当前任务状态为NEW但是使用CAS设置当前任务的持有者为当前线程失败则直接返回

       代码(13)具体调用callable的call方法执行任务。这里在调用前又判断了任务的状态是否为NEW,是为了避免在执行代码(12)后其他线程修改了任务的状态(比如取消了该任务)。\

       如果任务执行成功则执行代码(13.2)修改任务状态,set方法的代码如下。

    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();
        }
    }
  • 首先使用CAS将当前任务的状态从NEW转换到COMPLETING。这里当有多个线程调用时只有一个线程会成功。成功的线程再通过UNSAFE.putOrderedInt设置任务的状态为正常结束状态,这里没有使用CAS是因为对于同一个任务只可能有一个线程运行到这里。
  • 使用putOrderedInt比使用CAS或者putLongvolatile效率要高,并且这里的场景不要求其他线程马上对设置的状态值可见

 参考:

       https://artisan.blog.csdn.net/article/details/121873293?spm=1001.2014.3001.5502

posted on 2022-01-16 18:20  溪水静幽  阅读(45)  评论(0)    收藏  举报