Java定时任务(2)

来源:互联网 发布:淘宝森女部落有没有山 编辑:程序博客网 时间:2024/05/21 17:56

Java定时任务(2)


上次浅显的分析了Timer及TimerTask的调度原理,这里我们再来看一下另一种定时调度方式ScheduledThreadPoolExecutor的内部执行原理。

  • ScheduledThreadPoolExecutor调度方式
  • 阻塞队列DelayedWorkQueue的实现
  • ScheduledFutureTask控制调度过程
  • 总结

ScheduledThreadPoolExecutor调度方式

类似于Timer,它的调度方式也有4种,我们抽出其中一种来具体分析。

//一次性任务,无返回值public ScheduledFuture<?> schedule(Runnable command,                                       long delay,                                       TimeUnit unit) {...}//一次性任务,有返回值public <V> ScheduledFuture<V> schedule(Callable<V> callable,                                           long delay,                                           TimeUnit unit) {...}public ScheduledFuture<?> scheduleAtFixedRate(Runnable command,                                                  long initialDelay,                                                  long period,                                                  TimeUnit unit) {...}public ScheduledFuture<?> scheduleWithFixedDelay(Runnable command,                                                     long initialDelay,                                                     long delay,                                                     TimeUnit unit){...}

我们取scheduleWithFixedDelay作为切入点来进行分析。

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);        sft.outerTask = t;        delayedExecute(t);        return t;    }//将Runnable适配成FutureTaskScheduledFutureTask<Void> sft =            new ScheduledFutureTask<Void>(command,                                          null,                                          triggerTime(initialDelay, unit),                                          unit.toNanos(-delay));//由于是将一个无返回值的Runnable包装成一个FutureTask,所以传入一个null作为返回值 ScheduledFutureTask(Runnable r, V result, long ns, long period) {            super(r, result);            this.time = ns;         //设置执行时间            this.period = period;   //设置时间片            this.sequenceNumber = sequencer.getAndIncrement();        }//Executors.callable()是真正进行Runnable与FutureTask适配的函数public FutureTask(Runnable runnable, V result) {        this.callable = Executors.callable(runnable, result);        this.state = NEW;       // ensure visibility of callable    }public static <T> Callable<T> callable(Runnable task, T result) {        if (task == null)            throw new NullPointerException();        return new RunnableAdapter<T>(task, result);    }    //call()就不多做介绍了,由FutureTask的call来掉内部Runnable的run()static final class RunnableAdapter<T> implements Callable<T> {        final Runnable task;        final T result;        RunnableAdapter(Runnable task, T result) {            this.task = task;            this.result = result;        }        public T call() {            task.run();            return result;        }    }    //修改或替换用于执行一个可运行的任务。    //这个方法可以用来覆盖具体类以管理内部任务。    //默认实现仅返回给定的任务。    RunnableScheduledFuture<Void> t = decorateTask(command, sft);    protected <V> RunnableScheduledFuture<V> decorateTask(        Runnable runnable, RunnableScheduledFuture<V> task) {        return task;    }//这里之所以先将task放入队列,再由worker去取出并运行,是因为task有可能并不期望当下就被运行,而是期望一段时间的延迟后再开始运行。//同时一个task被创建后直接运行,也是不合理的一种行为。private void delayedExecute(RunnableScheduledFuture<?> task) {        if (isShutdown())       //线程池状态是否正常            reject(task);       //拒绝任务,根据拒绝策略,通常有1.直接丢弃 2.抛异常 3.新建一个线程来执行 4.类似于LRU,丢弃队列中最久的一个任务        else {            super.getQueue().add(task);         //先加入BlockingQueue,该队列默认在ThreadPoolExecutor中实现,但这里提供了DelayedWorkQueue代替默认实现            if (isShutdown() &&                !canRunInCurrentRunState(task.isPeriodic()) &&      //当task已经被加入队列,但是线程池挂了,根据status来决定,取消并删除在队列中的task。                remove(task))                task.cancel(false);            else                ensurePrestart();        }    }//其实就是保证至少有一个worker已经启动    void ensurePrestart() {        int wc = workerCountOf(ctl.get());        if (wc < corePoolSize)            addWorker(null, true);        else if (wc == 0)            addWorker(null, false);    }

至此,一个定时任务就被加入DelayedWorkQueue队列了,而调度任务的线程由ThreadPoolExecutor提供。
但是到此为此还并没有实现延迟调度的功能。
接下来说说DelayedWorkQueue是如何实现延迟调度的。


阻塞队列DelayedWorkQueue的实现

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();     //重建数组,长度增加50%                size = i + 1;                   if (i == 0) {                    queue[0] = e;                    setIndex(e, 0);                } else {                    siftUp(i, e);                }                if (queue[0] == e) {                    leader = null;                    available.signal();                }            } finally {                lock.unlock();            }            return true;        }//这点跟Timer中的操作比较类似,做了一个相对排序而非绝对意义的排序。//内部做了一个比较接口,根据下次执行的时间来排队。private void siftUp(int k, RunnableScheduledFuture<?> key) {            while (k > 0) {                int parent = (k - 1) >>> 1;                RunnableScheduledFuture<?> e = queue[parent];                if (key.compareTo(e) >= 0)                    break;                queue[k] = e;                setIndex(e, k);                k = parent;            }            queue[k] = key;            setIndex(key, k);        }//但是显然offer并没有来提供延迟调度的实现,真正的延迟调度的实现是在take中完成的。 public RunnableScheduledFuture<?> take() throws InterruptedException {            final ReentrantLock lock = this.lock;            lock.lockInterruptibly();            try {                for (;;) {                    RunnableScheduledFuture<?> first = queue[0];    //第一个元素,也就是优先级最高,最应该被执行的任务                    if (first == null)                        available.await();  //进入这里说明队列中没有待执行的任务,所以来取任务的worker进入等待状态                    else {                        long delay = first.getDelay(NANOSECONDS);   //取到任务,getDelay定义于内部类ScheduledFutureTask中                        if (delay <= 0)                            return finishPoll(first);               //下面有说明,建议先看下面                        first = null; // don't retain ref while waiting                        if (leader != null)                            available.await();                        else {                            Thread thisThread = Thread.currentThread();                            leader = thisThread;                            try {                                available.awaitNanos(delay);        //进入这里说明,当前还未到该任务下次执行的时间,所以进入等待,等待时长为当前到执行时间的时差                            } finally {                                if (leader == thisThread)                                    leader = null;                            }                        }                    }                }            } finally {                if (leader == null && queue[0] != null)                    available.signal();                lock.unlock();            }        }//其目的是计算出下次将要执行任务的时间与当前系统时间的差值。//若小于等于0,则说明该立即被执行。//否则说明仍需等待。 public long getDelay(TimeUnit unit) {            return unit.convert(time - now(), NANOSECONDS);        }//返回第一个task,并将第一个元素与最后一个元素互换位置。//通常因为我们在offer一个task的时候已经进行过相对排序,使得这个队列在大体上是相对有序的。//而这里在take走一个元素后却将最后一个元素(理论上来说,无论是优先级是相对低的,下次执行时间都是最久的)互换。 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;        }private void siftDown(int k, RunnableScheduledFuture<?> key) {            int half = size >>> 1;            while (k < half) {                int child = (k << 1) + 1;                RunnableScheduledFuture<?> c = queue[child];                int right = child + 1;                if (right < size && c.compareTo(queue[right]) > 0)                    c = queue[child = right];                if (key.compareTo(c) <= 0)                    break;                queue[k] = c;                setIndex(c, k);                k = child;            }            queue[k] = key;            setIndex(key, k);        }

siftDown()大体上是一个自顶向下的一个相对排序。
首先half = size >>> 1 = size/2。
其次,关注whlie(k < half),我们假设永不会走到break,下面我简单的列出部分数组下标变化情况。
假设 size = 32。

k child right 0 1 2 1 3 4 3 7 8 7 15 16 15 31 32

由于条件是k < half
所以至多循环log2(size)次。
类比于siftUp,其实是一次自顶向下的相对排序,结果也会是相对有序,我猜可能是为了弥补siftUp()时的不足,所以再进行了一次相对排序。
至此,已经从大体上了解了DelayedWorkQueue是如何实现延迟调度的。

最后,在来看下worker取到task之后如何run
由于task被ScheduledFutureTask包装,所以我们看的自然是ScheduledFutureTask的run()。


ScheduledFutureTask控制调度过程

public void run() {        boolean periodic = isPeriodic();    //判断是否是一个循环任务        if (!canRunInCurrentRunState(periodic))            cancel(false);        else if (!periodic)            ScheduledFutureTask.super.run();    //若不是循环任务,当做一个普通任务来处理        else if (ScheduledFutureTask.super.runAndReset()) { //若是循环任务,在runAndReset()中实际运行,运行完毕后同时将status重新置为NEW,也就是相当于一个新task            setNextRunTime();       //计算下次任务执行的时间,这里与Timer有所区别,Timer是先计算出来在运行,而这里是先运行在计算            reExecutePeriodic(outerTask);       //自然,它完成的就是讲task重新放入队列,并做一些线程池状态的相关判断        }    }

总结

1.延迟调度由DelayedWorkQueue来实现。
2.Executors.callable()完成Runnable到Callable的转换(适配)。
3.ScheduledFutureTask包装task,实现循环任务的“循环”处理,延迟计算,调度控制等相关操作。
4.ThreadPoolExecutor实现多线程并发操作。
5.相比于Time,最为明显的几点:
  1.Timer单线程,ScheduledThreadPoolExecutor多线程(显而易见)。
  2.Timer先计算下次调度时间,在运行,ScheduledThreadPoolExecutor相反。
  3.Timer之间无法实现TimerTask共享,ScheduledThreadPoolExecutor通过ThreadPoolExecutor可以实现。
  4.Timer与ScheduledThreadPoolExecutor使用的BlockingQueue不同。

0 0
原创粉丝点击