ThreadPoolExecutor(八)——ScheduledThreadPoolExecutor

1.ScheduledThreadPoolExecutor的原理

ScheduledThreadPoolExecutor本质上还是ThreadPoolExecutor,所以调度上还是会执行ThreadPoolExecutor的流程,只是在几个地方有所不同,主要是因为DelayedWorkQueue和ScheduledFutureTask。

case1:当core线程数不够的时候执行schedule

ScheduledExecutorService scheduledExecutorService = new ScheduledThreadPoolExecutor(1);
        long start = System.currentTimeMillis();
        scheduledExecutorService.schedule(new Runnable() {
            @Override public void run() {
                LOG.info("{}, task 1 delayed:{}", System.currentTimeMillis() - start, Thread.currentThread().getName());
                Uninterruptibles.sleepUninterruptibly(10, TimeUnit.SECONDS);
            }
        }, 2, TimeUnit.SECONDS);
        long start2 = System.currentTimeMillis();
        scheduledExecutorService.schedule(new Runnable() {
            @Override public void run() {
                LOG.info("{}, task 2 delayed:{}", System.currentTimeMillis() - start2,
                        Thread.currentThread().getName());
            }
        }, 5, TimeUnit.SECONDS);

结果:

因为core设置为1,第一个任务占用了唯一的core线程,按照线程池调度策略,第二个任务进入了队列,等待第一个任务执行完。如果第一个任务执行时间过长,比如这个case中的执行了10s,第二个任务等待后调度执行的时机就已经不准确。下面是核心代码:

public RunnableScheduledFuture<?> take() throws InterruptedException {
            final ReentrantLock lock = this.lock;
            lock.lockInterruptibly();
            try {
                for (;;) {
                    RunnableScheduledFuture<?> first = queue[0];
                    if (first == null)
                        available.await();
                    else {
                        long delay = first.getDelay(NANOSECONDS);
                        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();
            }
        }

上面第一个task对应的runnable,即task1,走的逻辑分支是

else {
                            Thread thisThread = Thread.currentThread();
                            leader = thisThread;
                            try {
                                available.awaitNanos(delay);
                            } finally {
                                if (leader == thisThread)
                                    leader = null;
                            }
                        }

delay是2秒,正常等待2秒后执行,执行的任务因为有10秒的sleep所以时间比较久,超过了task2的等待时间。

task1结束之后,回到父类核心的runWorker方法中

final void runWorker(Worker w) {
        Thread wt = Thread.currentThread();
        Runnable task = w.firstTask;
        w.firstTask = null;
        w.unlock(); // allow interrupts
        boolean completedAbruptly = true;
        try {
            while (task != null || (task = getTask()) != null) {

继续getTask,继续调用DelayedWorkQueue的take方法,第二次走到的分支是

else {
                        long delay = first.getDelay(NANOSECONDS);
                        if (delay <= 0)
                            return finishPoll(first);

这里delay是负数,所以会立即执行,但也已为时已晚,比应该的执行时间晚了7秒钟。

如果这里把case写成

 ScheduledExecutorService scheduledExecutorService = new ScheduledThreadPoolExecutor(2);
        long start = System.currentTimeMillis();
        scheduledExecutorService.schedule(new Runnable() {
            @Override public void run() {
                LOG.info("{}, task 1 delayed:{}", System.currentTimeMillis() - start, Thread.currentThread().getName());
                Uninterruptibles.sleepUninterruptibly(10, TimeUnit.SECONDS);
            }
        }, 2, TimeUnit.SECONDS);
        long start2 = System.currentTimeMillis();
        scheduledExecutorService.schedule(new Runnable() {
            @Override public void run() {
                LOG.info("{}, task 2 delayed:{}", System.currentTimeMillis() - start2,
                        Thread.currentThread().getName());
            }
        }, 5, TimeUnit.SECONDS);

就不会出现上述问题了,因为task2的时候core线程没有满,所以会再拉起一个core线程去执行task2,即使task1还在占用第一个core线程,执行结果如下:

 看到task2被线程2执行了,执行时间是在预期的5秒之后。

case2:当core线程数不够的时候执行scheduleAtFixedRate

ScheduledExecutorService scheduledExecutorService = new ScheduledThreadPoolExecutor(2);
        long start = System.currentTimeMillis();
        AtomicInteger integer = new AtomicInteger(0);
        scheduledExecutorService.scheduleAtFixedRate(new Runnable() {
            @Override public void run() {
                LOG.info("{}, task {} delayed:{}", Thread.currentThread().getName(), integer.addAndGet(1),
                        System.currentTimeMillis() - start);
                Uninterruptibles.sleepUninterruptibly(4, TimeUnit.SECONDS);
            }
        }, 0, 2, TimeUnit.SECONDS);

结果:

这里和执行schedule不太一样,区别在ScheduledFutureTask的run方法

public void run() {
            boolean periodic = isPeriodic();
            if (!canRunInCurrentRunState(periodic))
                cancel(false);
            else if (!periodic)
                ScheduledFutureTask.super.run();
            else if (ScheduledFutureTask.super.runAndReset()) {
                setNextRunTime();
                reExecutePeriodic(outerTask);
            }
        }

这里会走最后一个else if分支,执行runAndReset,会调用setNextRunTime和reExecutePeriodic

private void setNextRunTime() {
            long p = period;
            if (p > 0)
                time += p;
            else
                time = triggerTime(-p);
        }

这里因为第一个task执行时间为4秒,超过间隔2秒,所以time+p已经是过去时了,

if (canRunInCurrentRunState(true)) {
            super.getQueue().add(task);
            if (!canRunInCurrentRunState(true) && remove(task))
                task.cancel(false);
            else
                ensurePrestart();
        }

然后这时才会把task加入到队列里,但long delay = first.getDelay(NANOSECONDS);,这里delay按上面说法,已经是过去时了,是负数,所以会立即执行,和case1一样已为时已晚。

和case1不同的是,这里的core即使是2,也于事无补,因为新的task只会在老的task结束之后才会补充到queue里,可以说是输在了起跑线上。

还有一点,因为在delayedExecute的时候ensurePrestart,所以core线程提前被启动了,所以不会再启动第二个core线程,除非再开一个scheduleAtFixedRate的任务,所以也没法通过扩大core来增加并行。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值