简介
上一篇文章剖析了ThreadPoolExecutor的原理和实现,这里剖析下ScheduledThreadPoolExecutor的实现方式。ScheduledThreadPoolExecuto能够执行延迟固定时间的任务和周期性任务。在需要多工作线程的情况下,它比Timer更方便,同时比ThreadPoolExecutor有更大灵活性和能力。
但是在使用ScheduledThreadPoolExecutor时,有几个点必须注意:
1.在执行延迟任务时,任务的执行不会早于你设定的延迟时间,但是也不保证恰好在达到你设定的延迟时间时就马上执行。在同一时间开始执行的任务,按照FIFO顺序提交给线程。
2.虽然ScheduledThreadPoolExecutor继承了ThreadPoolExecutor。但是针对ThreadPoolExecutor的一些性能调优的方法不一定试用于ScheduledThreadPoolExecutor。在ThreadPoolExecutor中,调整corePoolSize,maximumPoolSize两个参数可以控制任务的执行速度,例如当前线程小于corePoolSize时,可以马上为新任务创建线程而不必等待,当前线程大于corePoolSize小于maximumPoolSize。可以动态扩展当前线程以提高并发速度等。但是对于ScheduledThreadPoolExecutor,它实际上是一个固定线程数,无界阻塞队列的线程池,所有任务都是先放到工作队列中,而不会有直接为其创建线程的优待。所以调整corePoolSize,maximumPoolSize对ScheduledThreadPoolExecutor来说,性能上不会有影响。
实现原理
ScheduledThreadPoolExecutor继承了ThreadPoolExecutor,它先把用户提交的任务进一步封装成ScheduledFutureTask。再把ScheduledFutureTask放到任务队列中,交给线程池的线程去执行。可以说ScheduledThreadPoolExecutor与ThreadPoolExecutor的最大区别就是多了一步封装ScheduledFutureTask的过程。其他像任务队列的执行,线程的管理等都于ThreadPoolExecutor差不多,甚至就是直接使用了ThreadPoolExecutor的方法。下面就主要研究下ScheduledThreadPoolExecutor与ThreadPoolExecutor的不同地方。与ThreadPoolExecutor相关的这里直接省略,需要参考ThreadPoolExecutor的地方,请移步ThreadPoolExecutor的原理和实现。
ScheduledFutureTask的设计
把用户任务进一步封装成ScheduledFutureTask是ScheduledThreadPoolExecutor与ThreadPoolExecutor的主要区别。因此ScheduledFutureTask的设计就是ScheduledThreadPoolExecutor的核心。ScheduledFutureTask是ScheduledThreadPoolExecutor的一个内部类。ScheduledFutureTask继承了FutureTask。先看下ScheduledFutureTask独有的属性:
属性 | 属性描述 |
long sequenceNumber | 跳出FIFO调度关系的序列号 |
long time | 激活任务的时间,单位是纳秒。注意:这个时间不是需要延迟的时间。而是用纳秒表示的自然时间。 |
long period | 周期性任务的时间间隔,单位是纳秒。正数表示固定频率,负数表示固定的延迟时间。 |
ScheduledFutureTask提供了三种构造方法:
1.ScheduledFutureTask(Runnable r, V result, long ns)。 创建设定了任务执行的触发时间的一次性任务
1.ScheduledFutureTask(Runnable r, V result, long ns, long period)。创建设定了任务触发时间和周期时间的周期性任务。
2.ScheduledFutureTask(Callable<V> callable, long ns)。创建Callable,固定触发时间的一次性任务。
ScheduledFutureTask继承自FutureTask,所以需要实现自己的run方法:
public void run() { if (isPeriodic()) runPeriodic(); else ScheduledFutureTask.super.run(); }
如果是一次性任务,则执行FutureTask的run方法,如果是周期性任务,则执行runPeriodic()方法。下面看下runPeriodic()做了什么事情:
private void runPeriodic() { boolean ok = ScheduledFutureTask.super.runAndReset(); boolean down = isShutdown(); // Reschedule if not cancelled and not shutdown or policy allows if (ok && (!down || (getContinueExistingPeriodicTasksAfterShutdownPolicy() && !isStopped()))) { long p = period; if (p > 0) time += p; else time = triggerTime(-p); ScheduledThreadPoolExecutor.super.getQueue().add(this); } // This might have been the final executed delayed // task. Wake up threads to check. else if (down) interruptIdleWorkers(); }
首先,先执行FutureTask的runAndReset()。这个方法尝试执行一次用户任务,重置状态,并返回是否成功执行。判断下状态:任务成功执行了,线程池没有shutdown,任务没有被取消。那么设置任务的下一次执行时间。这里设置下次执行时间比较有意思如果period>0则time=time+p,若果period<=0,time=now()+p。即前者是上次任务开始的时间加上时间周期,后置是上一次任务执行完成后的时间加上时间周期。这样说有点拗口,换种方式来说:当把时间周期设置为正数,那么表示该任务每多长时间执行一次,任务的执行频率是固定的。如果间周期设置为负数,表示前一次任务执行完之后间隔多长时间开始执行下一次任务,每个任务之间的延时是固定的。
ScheduledThreadPoolExecutor的实现
ScheduledThreadPoolExecutor继承了ThreadPoolExecutor并且实现了ScheduledExecutorService接口。
ScheduledExecutorService定义了使用ScheduledThreadPoolExecutor提交任务的接口schedule。schedule是一组返回值和参数不同的overwrite接口。以供用户选择提交任务的模式,比如是一次性的定时任务还是周期性的任务,需不需要返回结果等等。不管是哪个schedule接口,他的实现功能主要是把用户提交的任务和模式封装成ScheduledFutureTask,然后调用delayedExecute(Runnable command)。所以delayedExecute方法是真正的处理任务的方法,代码如下:
private void delayedExecute(Runnable command) { if (isShutdown()) { reject(command); return; } // Prestart a thread if necessary. We cannot prestart it // running the task because the task (probably) shouldn't be // run yet, so thread will just idle until delay elapses. if (getPoolSize() < getCorePoolSize()) prestartCoreThread(); super.getQueue().add(command); }
如果线程池shutdown了,则执行reject策略。如果当前线程小于核心线程,那么调用prestartCoreThread()创建一个线程线程。prestartCoreThread()实际上是调用了ThreadPoolExecutor类里的addIfUnderCorePoolSize(null)。如果你看了ThreadPoolExecutor的分析。就会知道这个addIfUnderCorePoolSize(null)的参数设置为null其实就是只创建线程,而不会把当前任务直接提交给这个线程。任务直接通过super.getQueue().add(command);放到任务队列里去了。这也解释了前面简介中第2点说了为什么调整corePoolSize也不会让任务马上执行了,不管你corePoolSize调多大,你的任务还是需要排队,只是你的线程多了,可能任务排队等待的时间少了。
另外这里也可以解释简介中第1点注意事项:对于定时任务,只能保证执行时间不会早于你设定的时间。但不一定会恰好在你设定的时间点上执行。因为你提交的任务都是排队的。加入你12点00分00秒提交了一个任务,希望这个任务在12点00分05秒的时刻执行。加入你的线程任务处理的足够快,在12点00分03秒的时候这个任务得到了一个线程,那么这个线程开始执行你的任务,发现你的执行时间是12点00分05秒,那么线程会等2秒到12点00分05秒的时候开始执行你的任务;但是如果线程池线程数比较少或者任务执行比较耗时等,可能使你的任务在12点00分10秒的时刻才分配到了一个线程,然后线程发现你的任务已经过了到了执行时间,马上开始执行你的任务,这种情况下,相当于任务比你设定的时间延迟了5秒。
相关推荐
Java并发编程中的线程池是提高系统效率的关键工具,它解决了频繁创建和销毁线程的问题。线程池通过复用已存在的线程来处理任务,从而避免了每次任务执行完毕后销毁线程的开销。在Java中,线程池的核心实现是`java....
23 高并发编程和线程池,教程视频:java中高并发编程和线程池
线程池是并发编程中一个非常重要的概念,它主要用于管理和控制线程资源,提高程序的执行效率和响应速度。本文将详细解读Java中Executor框架的线程池原理和源码,同时探讨线程池的调优和监控方法。 首先,线程池的...
总的来说,这本书全面覆盖了高并发编程中的关键知识点,包括多线程原理、线程池实现、并发控制、分布式系统架构以及面试技巧,适合有志于提升并发编程能力的后端开发者阅读,尤其是对分布式锁和Java并发编程感兴趣的...
课程内容包括了JAVA手写线程池,UC线程池API详解,线程安全根因详解,锁与原子类,分布式锁原理与实现方式,并发编程-AQS等等针对性非常强的JAVA编程开发教程,这其中的内容对JAVA开发技能的拔尖,非常的有帮助。...
并发编程之线程与线程池.pptx 内部培训PPT 多线程 线程池
`Future`接口提供了获取异步计算结果的能力,它是Java并发编程中重要的API之一。 ##### 3.1 FutureTask `FutureTask`是`Future`接口的一个具体实现,它可以包装一个`Runnable`或`Callable`对象,并允许用户获取...
Java并发编程中的JUC线程池是Java程序员必须掌握的关键技术之一,它允许开发者高效地管理并发执行的任务,充分利用多核处理器的性能。线程池的出现解决了在并发环境中线程创建、销毁带来的开销,提高了系统资源的...
而线程池技术,则是实现高效异步任务处理的重要手段之一。Django异步任务线程池实现原理主要涉及以下几个核心知识点: 1. 异步任务执行原理: 当Django应用在处理耗时的任务时,通常会阻塞主线程,导致用户在等待...
《Java并发编程实战》是Java并发编程领域的一本经典著作,它深入浅出地介绍了如何在Java平台上进行高效的多线程编程。这本书的源码提供了丰富的示例,可以帮助读者更好地理解书中的理论知识并将其应用到实际项目中。...
【标题】"火山安卓编程线程池例子"主要聚焦于Android平台上的多线程编程,尤其是使用火山编程框架实现线程池的...通过对这个例子的学习,开发者能够深入理解线程池的工作原理和优势,提升其在Android并发编程中的技能。
《Java并发编程实战》这本书是关于Java语言中并发编程技术的经典著作。它详细介绍了如何在Java环境中有效地实现多线程程序和并发控制机制。在Java平台上,由于其本身提供了强大的并发编程支持,因此,掌握并发编程...
这部分内容有助于开发者理解并发编程的基本原理和最佳实践。 第二部分讨论了如何构建并发应用程序,涉及任务分解、线程池的高级特性,以及如何提高图形用户界面(GUI)的响应性。这部分内容强调了并发应用的设计和...
4. **线程池**:Java的Executor框架是管理线程的重要工具,书中会详细介绍ThreadPoolExecutor的工作原理,以及如何配置和使用线程池来优化并发性能。 5. **并发设计模式**:书中可能会介绍一些常见的并发设计模式,...
java并发编程_线程池的使用方法 java并发编程中,线程池是一种非常重要的概念,它可以帮助我们更好地管理线程,提高程序的性能和可扩展性。下面我们将详细介绍java线程池的使用方法。 一、任务和执行策略之间的...
在IT领域,尤其是在系统编程和...总的来说,C语言的线程池编程涉及到对pthreads库的深入理解和应用,以及对并发控制和任务调度策略的掌握。通过合理设计和实现线程池,可以显著提升多任务环境下的系统性能和响应速度。
│ 高并发编程第一阶段35讲、线程池原理与自定义线程池.mp4 │ 高并发编程第一阶段36讲、自定义个简单的线程池并且测试.mp4 │ 高并发编程第一阶段37讲、给线程池增加拒绝策略以及停止方法.mp4 │ 高并发编程第...
在Java并发编程中,多线程是核心概念之一。多线程允许程序同时执行多个任务,从而充分利用系统资源,提高程序性能。然而,多线程编程也带来了同步和竞态条件等问题,这需要开发者具备良好的线程管理和同步机制的知识...
### C++线程池实现原理分析 #### 一、引言 线程池是一种软件设计模式,用于管理和控制大量线程的创建与销毁,尤其是在处理大量短期任务时,它可以显著提高程序性能。线程池的核心思想是预先创建一组线程,并让它们...