一文读懂Java线程池ScheduledThreadPoolExecutor的使用与运行机制

ScheduledThreadPoolExecutor继承自ThreadPoolExecutor。它主要用来在给定的延迟之后运行任务,或者定期执行任务。ScheduledThreadPoolExecutor的功能与Timer类似,但 ScheduledThreadPoolExecutor功能更强大、更灵活。Timer对应的是单个后台线程,而 ScheduledThreadPoolExecutor可以在构造函数中指定多个对应的后台线程数。

和ThreadPoolExecutor类似,ScheduledThreadPoolExecutor也支持两种方式的使用,第一种为通过构造方法创建实例,第二种则是通过Executors创建,如下为创建ScheduledThreadPoolExecutor的两种方式:

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

ScheduledThreadPoolExecutor提供了四个方法用分别在给定的延迟时间之后运行任务和定时执行任务,如下为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){}

下面我们scheduleWithFixedDelay方法为示例,讲解ScheduledThreadPoolExecutor的使用,如下我们写一个Runnable线程,里面有一个变量i增加,并且sleep i秒。代码如下:

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

我们在实例化ScheduledThreadPoolExecutor时,我们可以看到ScheduledThreadPoolExecutor的构造方法,其在内部构造了一个DelayQueue,代码如下:

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

DelayQueue是一个无界队列,当调用ScheduledThreadPoolExecutor的scheduleAtFixedRate()方法或者scheduleWith-
FixedDelay()方法时,会向ScheduledThreadPoolExecutor的DelayQueue添加一个实现了RunnableScheduledFutur接口的ScheduledFutureTask。线程池中的线程从DelayQueue中获取ScheduledFutureTask,然后执行任务。

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会把待调度的任务(ScheduledFutureTask)放到一个DelayQueue中。ScheduledFutureTask主要包含3个成员变量分别为:time,表示这个任务将要被执行的具体时间;sequenceNumber,表示这个任务被添加到ScheduledThreadPoolExecutor中的序号;period,表示任务执行的间隔周期。DelayQueue封装了一个PriorityQueue,这个PriorityQueue会对队列中的ScheduledFutureTask进行排序。排序时,time小的排在前面(时间早的任务将被先执行)。如果两个ScheduledFutureTask的time相同,就比较sequenceNumber,sequenceNumber小的排在前面(也就是说,如果两个任务的执行时间相同,那么先提交的任务将被先执行)。

在介绍ScheduledThreadPoolExecutor如何把任务放入到队列之后,我们下面再看,ScheduledThreadPoolExecutor是如何从队列中取出任务并且执行的。也就是ScheduledThreadPoolExecutor的执行周期。一般来说需要四个步骤如下图所示:

上面的步骤中首先从DelayQueue中获取已到期的ScheduledFutureTask(DelayQueue.take())。然后执行这个ScheduledFutureTask。并且将ScheduledFutureTask的time变量修改为下次将要被执行的时间。最后把ScheduledFutureTask放回DelayQueue中。首先我们查看是如何从队列中取出任务的:

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

最后我们分析最后,ScheduledThreadPoolExecutor中的线程执行任务的步骤4,把ScheduledFutureTask放入DelayQueue中的过程。下面是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;
}

猜你喜欢

转载自blog.csdn.net/wk19920726/article/details/108623388