An article to understand the use and operating mechanism of the Java thread pool ScheduledThreadPoolExecutor

ScheduledThreadPoolExecutor inherits from ThreadPoolExecutor. It is mainly used to run tasks after a given delay, or to perform tasks on a regular basis. The function of ScheduledThreadPoolExecutor is similar to Timer, but the function of ScheduledThreadPoolExecutor is more powerful and flexible. Timer corresponds to a single background thread, and ScheduledThreadPoolExecutor can specify multiple corresponding number of background threads in the constructor.

Similar to ThreadPoolExecutor, ScheduledThreadPoolExecutor also supports two ways to use. The first is to create an instance through the construction method, and the second is to create it through Executors. The following are two ways to create a ScheduledThreadPoolExecutor:

ScheduledExecutorService scheduledExecutorService = Executors.newScheduledThreadPool(10);
ScheduledThreadPoolExecutor executor = new ScheduledThreadPoolExecutor(10);

ScheduledThreadPoolExecutor provides four methods to run tasks and timed execution tasks after a given delay time. The following is the analysis of the four methods provided by ScheduledThreadPoolExecutor:

//command为要执行的任务,delay指定在delay时间之后执行,unit为时间单位
public ScheduledFuture<?> schedule(Runnable command,long delay,TimeUnit unit){......}
//callable为要执行的任务,delay指定在delay时间之后执行,unit为时间单位
//区别是一个有返回值,一个没返回值
public <V> ScheduledFuture<V> schedule(Callable<V> callable,long delay,TimeUnit unit){......}
//command为要执行的任务,以period为固定周期时间,按照一定频率来重复执行任务,initialDelay是说系统启动后,等待initialDelay时间开始执行
 public ScheduledFuture<?> scheduleAtFixedRate(Runnable command,long initialDelay,long period,TimeUnit unit) {
}
//这个是以delay为固定延迟时间,按照一定的等待时间来执行任务,initialDelay意义与上面的相同。
//该方法与上面的类似,但是含义又不同,上面的方法是以固定的频率执行,而该方法是在任务执行之后在delay时间后再次执行。
public ScheduledFuture<?> scheduleWithFixedDelay(Runnable command,long initialDelay,long delay,TimeUnit unit){}

Below we use the scheduleWithFixedDelay method as an example to explain the use of ScheduledThreadPoolExecutor. As follows, we write a Runnable thread with a variable i increase and sleep i seconds. code show as below:

AtomicInteger i = new AtomicInteger();
ScheduledThreadPoolExecutor executor = new ScheduledThreadPoolExecutor(2);
executor.scheduleWithFixedDelay(()->{
    System.out.println("执行定时任务");
},2, 2, TimeUnit.SECONDS);

When we instantiate ScheduledThreadPoolExecutor, we can see the construction method of ScheduledThreadPoolExecutor, which constructs a DelayQueue internally. The code is as follows:

public ScheduledThreadPoolExecutor(int corePoolSize) {
    super(corePoolSize, Integer.MAX_VALUE, 0, NANOSECONDS, new DelayedWorkQueue());
}

DelayQueue is an unbounded queue. When the scheduleAtFixedRate() method or scheduleWith-
FixedDelay() method of ScheduledThreadPoolExecutor is called, a ScheduledFutureTask that implements the RunnableScheduledFutur interface is added to the DelayQueue of ScheduledThreadPoolExecutor. The threads in the thread pool obtain ScheduledFutureTask from DelayQueue, and then execute the task.

public ScheduledFuture<?> scheduleWithFixedDelay(Runnable command,long initialDelay,long delay,TimeUnit unit) {
        ,.....
    、//将要执行的任务command封装为ScheduledFutureTask对象
    ScheduledFutureTask<Void> sft =new ScheduledFutureTask<Void>(command,null,triggerTime(initialDelay, unit),unit.toNanos(-delay));
    //将ScheduledFutureTask和command装饰为RunnableScheduledFuture
    RunnableScheduledFuture<Void> t = decorateTask(command, sft);
    sft.outerTask = t;
    //该方法会将任务放到DelayQueue中,代码如下:
    delayedExecute(t);
    return t;
}
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();
    }
}

ScheduledThreadPoolExecutor will put the task to be scheduled (ScheduledFutureTask) into a DelayQueue. ScheduledFutureTask mainly contains three member variables: time, which represents the specific time when this task will be executed; sequenceNumber, which represents the sequence number of this task to be added to ScheduledThreadPoolExecutor; period, which represents the interval between task execution. DelayQueue encapsulates a PriorityQueue, this PriorityQueue sorts the ScheduledFutureTask in the queue. When sorting, the smaller time is ranked first (the earlier task will be executed first). If the time of two ScheduledFutureTasks is the same, compare the sequenceNumber, and the smaller sequenceNumber is ranked first (that is, if the execution time of the two tasks is the same, the task submitted first will be executed first).

After introducing how ScheduledThreadPoolExecutor puts tasks into the queue, let's look at how ScheduledThreadPoolExecutor removes tasks from the queue and executes them. That is, the execution cycle of ScheduledThreadPoolExecutor. Generally speaking, four steps are required as shown in the figure below:

In the above steps, first obtain the expired ScheduledFutureTask (DelayQueue.take()) from DelayQueue. Then execute this ScheduledFutureTask. And modify the time variable of ScheduledFutureTask to the time that will be executed next time. Finally, put ScheduledFutureTask back into DelayQueue. First, let's see how to remove tasks from the queue:

public RunnableScheduledFuture<?> take() throws InterruptedException {
    final ReentrantLock lock = this.lock;
    lock.lockInterruptibly();
    try {
        for (;;) {
            //获取第一个队列中的任务
            RunnableScheduledFuture<?> first = queue[0];
            //如果PriorityQueue为空,当前线程到Condition中等待
            if (first == null)
                available.await();
            else {
                long delay = first.getDelay(NANOSECONDS);
                //如果時間<=0,實行任務
                if (delay <= 0)
                    return finishPoll(first);
                first = null; 
                if (leader != null)
                    available.await();
                else {
                    Thread thisThread = Thread.currentThread();
                    leader = thisThread;
                    try {   
                        //如果PriorityQueue的头元素的time时间比当前时间大,到Condition中等待到time时间
                        available.awaitNanos(delay);
                    } finally {
                    if (leader == thisThread)
                          leader = null;
                      }
                }
             }
         }
    } finally {
         if (leader == null && queue[0] != null)
              available.signal();
    lock.unlock();
    }
}

Finally, we analyze the final step 4 of the thread in the ScheduledThreadPoolExecutor to execute the task, the process of putting the ScheduledFutureTask into the DelayQueue. Below is the source code implementation of DelayQueue.add().

 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();
            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;
}

 

Guess you like

Origin blog.csdn.net/wk19920726/article/details/108623388