首页 > 代码库 > Java ScheduledExecutorService源码分析

Java ScheduledExecutorService源码分析

Java 定时任务可以用Timer + TimerTask来做,或者使用ScheduledExecutorService,使用ScheduledExecutorService有两个好处:

1. 如果任务执行时间过长,TimerTask会出现延迟执行的情况。比如,第一任务在1000ms执行了4000ms,第二个任务定时在2000ms开始执行。这里由于第一个任务要执行4000,所以第二个任务实际在5000ms开始执行。这是由于Timer是单线程,且顺序执行提交的任务

2. 如果执行任务抛出异常,Timer是不会执行会后面的任务的

使用ScheduledExecutorService可以避免上面两种情况,因为ScheduledExecutorService是线程池,有多个线程执行。

 

下面是一个使用ScheduledExecutorService实现定时任务的Demo

import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.ScheduledThreadPoolExecutor;
import java.util.concurrent.TimeUnit;

/**
 * Created by gxf on 2017/6/26.
 */
public class TestSchedule {
    public static void main(String[] args) {
        ScheduledExecutorService scheduledExecutorService = new ScheduledThreadPoolExecutor(5);
        Task task = new Task();
        scheduledExecutorService.scheduleAtFixedRate(task, -10, 1, TimeUnit.SECONDS);
    }
}

class Task implements Runnable{
    public void run(){
        System.out.println("do task...");
    }
}

这里每隔1秒,控制台会输出do task

ScheduledExecutorService是一个接口

public interface ScheduledExecutorService extends ExecutorService {

其中一个实现是

public class ScheduledThreadPoolExecutor
        extends ThreadPoolExecutor
        implements ScheduledExecutorService {

在Demo中

ScheduledExecutorService scheduledExecutorService = new ScheduledThreadPoolExecutor(5);

跟进ScheduledThreadPoolExecutor

/**
     * Creates a new {@code ScheduledThreadPoolExecutor} with the
     * given core pool size.
     *
     * @param corePoolSize the number of threads to keep in the pool, even
     *        if they are idle, unless {@code allowCoreThreadTimeOut} is set
     * @throws IllegalArgumentException if {@code corePoolSize < 0}
     */
    public ScheduledThreadPoolExecutor(int corePoolSize) {
        super(corePoolSize, Integer.MAX_VALUE, 0, NANOSECONDS,
              new DelayedWorkQueue());
    }

这里Super调用

public ThreadPoolExecutor(int corePoolSize,
                              int maximumPoolSize,
                              long keepAliveTime,
                              TimeUnit unit,
                              BlockingQueue<Runnable> workQueue) {
        this(corePoolSize, maximumPoolSize, keepAliveTime, unit, workQueue,
             Executors.defaultThreadFactory(), defaultHandler);
    }

设定了线程池的各个参数,核心线程数,最大线程数,任务队列等。但这里还有线程创建,有任务提交了,才会创建线程池

继续Demo中

Task task = new Task();

这里只是创建了一个Runable对象

继续Demo

scheduledExecutorService.scheduleAtFixedRate(task, -10, 1, TimeUnit.SECONDS);

这里已经把任务提交给线程池了,进入方法

/**
     * @throws RejectedExecutionException {@inheritDoc}
     * @throws NullPointerException       {@inheritDoc}
     * @throws IllegalArgumentException   {@inheritDoc}
     */
    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();
        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;
    }

前面都是一些条件检查和包装,看最后的delayedExecute(t)

/**
     * Main execution method for delayed or periodic tasks.  If pool
     * is shut down, rejects the task. Otherwise adds task to queue
     * and starts a thread, if necessary, to run it.  (We cannot
     * prestart the thread to run the task because the task (probably)
     * shouldn‘t be run yet.)  If the pool is shut down while the task
     * is being added, cancel and remove it if required by state and
     * run-after-shutdown parameters.
     *
     * @param task the task
     */
    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();
        }
    }

ok,这里可以看到任务被添加到了一个队列里面。在看最后,ensurePrestart()

/**
     * 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);
        else if (wc == 0)
            addWorker(null, false);
    }

这里可以看出,如果线程池里面的线程数,小于核心线程数,会继续添加线程。进入addWork(null, true)

 1 private boolean addWorker(Runnable firstTask, boolean core) {
 2         retry:
 3         for (;;) {
 4             int c = ctl.get();
 5             int rs = runStateOf(c);
 6 
 7             // Check if queue empty only if necessary.
 8             if (rs >= SHUTDOWN &&
 9                 ! (rs == SHUTDOWN &&
10                    firstTask == null &&
11                    ! workQueue.isEmpty()))
12                 return false;
13 
14             for (;;) {
15                 int wc = workerCountOf(c);
16                 if (wc >= CAPACITY ||
17                     wc >= (core ? corePoolSize : maximumPoolSize))
18                     return false;
19                 if (compareAndIncrementWorkerCount(c))
20                     break retry;
21                 c = ctl.get();  // Re-read ctl
22                 if (runStateOf(c) != rs)
23                     continue retry;
24                 // else CAS failed due to workerCount change; retry inner loop
25             }
26         }
27 
28         boolean workerStarted = false;
29         boolean workerAdded = false;
30         Worker w = null;
31         try {
32             w = new Worker(firstTask);
33             final Thread t = w.thread;
34             if (t != null) {
35                 final ReentrantLock mainLock = this.mainLock;
36                 mainLock.lock();
37                 try {
38                     // Recheck while holding lock.
39                     // Back out on ThreadFactory failure or if
40                     // shut down before lock acquired.
41                     int rs = runStateOf(ctl.get());
42 
43                     if (rs < SHUTDOWN ||
44                         (rs == SHUTDOWN && firstTask == null)) {
45                         if (t.isAlive()) // precheck that t is startable
46                             throw new IllegalThreadStateException();
47                         workers.add(w);
48                         int s = workers.size();
49                         if (s > largestPoolSize)
50                             largestPoolSize = s;
51                         workerAdded = true;
52                     }
53                 } finally {
54                     mainLock.unlock();
55                 }
56                 if (workerAdded) {
57                     t.start();
58                     workerStarted = true;
59                 }
60             }
61         } finally {
62             if (! workerStarted)
63                 addWorkerFailed(w);
64         }
65         return workerStarted;
66     }

第32行和33行可以看出,任务已经提交给Work类了,第57行,t.start()这里启动线程,执行提交的任务

到这里,提交的任务已经开始执行了。这里,我们在看一下Worker这个包装类

private final class Worker
        extends AbstractQueuedSynchronizer
        implements Runnable
    {
        /**
         * This class will never be serialized, but we provide a
         * serialVersionUID to suppress a javac warning.
         */
        private static final long serialVersionUID = 6138294804551838833L;

        /** Thread this worker is running in.  Null if factory fails. */
        final Thread thread;
        /** Initial task to run.  Possibly null. */
        Runnable firstTask;

可以看到有Thread, task,这里,其实就是把task指向要执行的任务,thread作为作为线程执行任务。

在ThreadPoolExecutor中

private final HashSet<Worker> workers = new HashSet<Worker>();

这个保存我们生成的线程,有了这个就不用重新创建和销毁线程了

我们在看一下Worker的run()方法

/** Delegates main run loop to outer runWorker  */
        public void run() {
            runWorker(this);
        }

继续跟进

 1 final void runWorker(Worker w) {
 2         Thread wt = Thread.currentThread();
 3         Runnable task = w.firstTask;
 4         w.firstTask = null;
 5         w.unlock(); // allow interrupts
 6         boolean completedAbruptly = true;
 7         try {
 8             while (task != null || (task = getTask()) != null) {
 9                 w.lock();
10                 // If pool is stopping, ensure thread is interrupted;
11                 // if not, ensure thread is not interrupted.  This
12                 // requires a recheck in second case to deal with
13                 // shutdownNow race while clearing interrupt
14                 if ((runStateAtLeast(ctl.get(), STOP) ||
15                      (Thread.interrupted() &&
16                       runStateAtLeast(ctl.get(), STOP))) &&
17                     !wt.isInterrupted())
18                     wt.interrupt();
19                 try {
20                     beforeExecute(wt, task);
21                     Throwable thrown = null;
22                     try {
23                         task.run();
24                     } catch (RuntimeException x) {
25                         thrown = x; throw x;
26                     } catch (Error x) {
27                         thrown = x; throw x;
28                     } catch (Throwable x) {
29                         thrown = x; throw new Error(x);
30                     } finally {
31                         afterExecute(task, thrown);
32                     }
33                 } finally {
34                     task = null;
35                     w.completedTasks++;
36                     w.unlock();
37                 }
38             }
39             completedAbruptly = false;
40         } finally {
41             processWorkerExit(w, completedAbruptly);
42         }
43     }

第8行getTask()取要执行的任务,第23行执行任务

继续跟进第8行getTask()

 1 private Runnable getTask() {
 2         boolean timedOut = false; // Did the last poll() time out?
 3 
 4         for (;;) {
 5             int c = ctl.get();
 6             int rs = runStateOf(c);
 7 
 8             // Check if queue empty only if necessary.
 9             if (rs >= SHUTDOWN && (rs >= STOP || workQueue.isEmpty())) {
10                 decrementWorkerCount();
11                 return null;
12             }
13 
14             int wc = workerCountOf(c);
15 
16             // Are workers subject to culling?
17             boolean timed = allowCoreThreadTimeOut || wc > corePoolSize;
18 
19             if ((wc > maximumPoolSize || (timed && timedOut))
20                 && (wc > 1 || workQueue.isEmpty())) {
21                 if (compareAndDecrementWorkerCount(c))
22                     return null;
23                 continue;
24             }
25 
26             try {
27                 Runnable r = timed ?
28                     workQueue.poll(keepAliveTime, TimeUnit.NANOSECONDS) :
29                     workQueue.take();
30                 if (r != null)
31                     return r;
32                 timedOut = true;
33             } catch (InterruptedException retry) {
34                 timedOut = false;
35             }
36         }
37     }

这里主要看第27-29,前面我们说过,任务是放到一个队列里面的。其实,是个阻塞队列,在队列为空的时候取,会阻塞线程。这里就用到了这个功能,在29行workQueue.take()如果没有任务,线程就会阻塞。有任务就会取任务进行执行。

 

简单点:线程池就是一个Set对象 + BlockingQueue对象, Workers + BlockingQueue

 

Java ScheduledExecutorService源码分析