Spring源码情操陶冶-任务定时器ConcurrentTaskScheduler
承接前文Spring源码情操陶冶#task:scheduled-tasks解析器,本文在前文的基础上讲解单核心线程线程池的工作原理
应用附例
承接前文的例子,如下
<!--define bean for schedule task-->
<bean id="taskBean" class="com.jing.test.spring.task.TaskBean"></bean>
<task:scheduled-tasks>
<task:scheduled ref="taskBean" method="doInit" cron="0 0 0 ? * *"></task:scheduled>
<task:scheduled ref="taskBean" method="doClear" cron="0 0 23 ? * *"></task:scheduled>
</task:scheduled-tasks>
即我们不配置task:scheduled-tasks的属性scheduler,则会采取org.springframework.scheduling.concurrent.ConcurrentTaskScheduler任务定时器。
实例化缘由
可以直接去看前文的ContextLifecycleScheduledTaskRegistrar#scheduleTasks()的一个代码片段,如下
protected void scheduleTasks() {
****
****
// 如果不指定scheduler属性,则默认使用单线程池模型
if (this.taskScheduler == null) {
this.localExecutor = Executors.newSingleThreadScheduledExecutor();
this.taskScheduler = new ConcurrentTaskScheduler(this.localExecutor);
}
****
}
我们可以观察下该定时器所包含的线程池对象,调用的是JDK Executors的静态方法newSingleThreadScheduledExecutor()方法
public static ScheduledExecutorService newSingleThreadScheduledExecutor() {
return new DelegatedScheduledExecutorService
(new ScheduledThreadPoolExecutor(1));
}
直接去看ScheduledThreadPoolExecutor构造函数
public ScheduledThreadPoolExecutor(int corePoolSize) {
// 应用ThreadPoolExecutor的构造方法,这里很熟悉了
super(corePoolSize, Integer.MAX_VALUE, 0, NANOSECONDS,
new DelayedWorkQueue());
}
从上面看出,创建的线程池类型为ScheduledThreadPoolExecutor,其内部情况如下
- 核心线程-1个
- 最大可创建线程-Integer.MAX_VALUE
- 空闲线程活动时间-0秒
- 线程队列-DelayedWorkQueue
- 拒绝策略-AbortPolicy(抛异常信息)
定时器执行入口
由前文得知,定时器会根据任务的类型执行TaskScheduler接口的scheduleAtFixedRate()或者scheduleWithFixedDelay()抑或是schedule()方法。为了方便理解,我们就拿schedule()方法入手
ConcurrentTaskScheduler#schedule()
@Override
public ScheduledFuture<?> schedule(Runnable task, Trigger trigger) {
try {
// jdk1.8不支持,jdk1.7支持
if (this.enterpriseConcurrentScheduler) {
return new EnterpriseConcurrentTriggerScheduler().schedule(decorateTask(task, true), trigger);
}
else {
// ErrorHandler一般为LoggingErrorHandler,打印错误日志,也可以直接抛出异常
ErrorHandler errorHandler = (this.errorHandler != null ? this.errorHandler : TaskUtils.getDefaultErrorHandler(true));
return new ReschedulingRunnable(task, trigger, this.scheduledExecutor, errorHandler).schedule();
}
}
catch (RejectedExecutionException ex) {
throw new TaskRejectedException("Executor [" + this.scheduledExecutor + "] did not accept task: " + task, ex);
}
}
由上述代码可知,最后包装为ReschedulingRunnable类来调用schedule()方法,OK,在此之前我们必须好好观察下ReschedulingRunnable
ReschedulingRunnable
首先看下其UML类图

再看下其构造函数
/**
* @param delegate ScheduledMethodRunnable.class Object
*
* @param trigger 一般为CronTrigger
*
* @param executor ScheduledThreadPoolExecutor.class Object
*
* @param errorHandler 一般为LoggingErrorHandler.class Object
*
*/
public ReschedulingRunnable(Runnable delegate, Trigger trigger, ScheduledExecutorService executor, ErrorHandler errorHandler) {
// 由父类保存真实的Runnable对象
super(delegate, errorHandler);
this.trigger = trigger;
this.executor = executor;
}
- 然后直接看下
schedule()方法的代码
public ScheduledFuture<?> schedule() {
synchronized (this.triggerContextMonitor) {
// 计算下一次执行的时间
this.scheduledExecutionTime = this.trigger.nextExecutionTime(this.triggerContext);
if (this.scheduledExecutionTime == null) {
return null;
}
// 由上述的执行时间计算需要延迟的时间,精确到毫秒
long initialDelay = this.scheduledExecutionTime.getTime() - System.currentTimeMillis();
// 由ScheduledThreadPoolExecutor执行定时任务
this.currentFuture = this.executor.schedule(this, initialDelay, TimeUnit.MILLISECONDS);
return this;
}
}
- 为了理解方便,我们不直接跳至
ScheduledThreadPoolExecutor看源码,我们脑壳想想,肯定会调用其中的run()方法
@Override
public void run() {
Date actualExecutionTime = new Date();
// 调用委托类DelegatingErrorHandlingRunnable的run方法
super.run();
// 更新时间戳并回调shedule()方法使其能周期性执行任务
Date completionTime = new Date();
synchronized (this.triggerContextMonitor) {
this.triggerContext.update(this.scheduledExecutionTime, actualExecutionTime, completionTime);
if (!this.currentFuture.isCancelled()) {
schedule();
}
}
}
直接看下父类DelegatingErrorHandlingRunnable#run()方法
@Override
public void run() {
try {
this.delegate.run();
}
catch (UndeclaredThrowableException ex) {
this.errorHandler.handleError(ex.getUndeclaredThrowable());
}
catch (Throwable ex) {
this.errorHandler.handleError(ex);
}
}
上述的代码则会去执行委托类ScheduledMethodRunnable的run()方法,脑壳想想就是通过JDK的method.invoke(Object obj,Object.. args)方法执行我们自定义写的业务。并由errorHandler捕获异常进行善后,默认只是进行error级别的日志输出。
- 对此类作下小结
执行定时器最终调用
ScheduledThreadPoolExecutor#schedule()方法,并返回ScheduledFuture对象其
run()方法最终会调用ScheduledMethodRunnable对象来执行用户自定义的业务其
run()方法也会调用schedule()方法来重复执行第一点的步骤以达到定时执行的效果
ConcurrentTaskScheduler如何保证单线程活跃执行任务
我们从上文得知,其corePoolSize=1,最终原因我们需要从ScheduledThreadPoolExecutor这个继承了ThreadPoolExecutor通用线程池类来了解
ScheduledThreadPoolExecutor#schedule()
直接看下源码
public ScheduledFuture<?> schedule(Runnable command,
long delay,
TimeUnit unit) {
if (command == null || unit == null)
throw new NullPointerException();
// 直接返回ScheduledFutureTask对象
RunnableScheduledFuture<?> t = decorateTask(command,
new ScheduledFutureTask<Void>(command, null,
triggerTime(delay, unit)));
// 延迟执行策略,继续追踪
delayedExecute(t);
return t;
}
继续追踪delayedExecute()方法
private void delayedExecute(RunnableScheduledFuture<?> task) {
// 如果线程池关闭了则直接拒绝任务
if (isShutdown())
reject(task);
else {
// 优先将任务放入DelayQueue队列
super.getQueue().add(task);
// 保护策略,避免添加到队列后线程池关闭了,尝试删除任务
if (isShutdown() &&
!canRunInCurrentRunState(task.isPeriodic()) &&
remove(task))
task.cancel(false);
else
// 确保已有线程在跑
ensurePrestart();
}
}
继续追踪ensurePrestart()方法
void ensurePrestart() {
int wc = workerCountOf(ctl.get());
if (wc < corePoolSize)
addWorker(null, true);
else if (wc == 0)
addWorker(null, false);
}
由上述的源码我们可以得知
当线程池数小于执行的核心线程数
corePoolSize,则会创建线程,不管核心与否我们都可以得知一旦当前线程数>=核心线程数,则不会进行新线程的创建
ConcurrentTaskScheduler指定的corePoolSize=1,由第一点得知,永远只有一个线程存在于线程池中
ConcurrentTaskScheduler如何保证任务延迟指定时长后被执行
为了避免大片的源码影响我们的阅读以及理解,博主只在此处指出ConcurrentTaskScheduler所拥有DelayQueue队列的take()方法实现了延迟等待的效果
public E take() throws InterruptedException {
final ReentrantLock lock = this.lock;
// 可重入同步锁,确保该方法线程安全
lock.lockInterruptibly();
try {
for (;;) {
E first = q.peek();
// 队列无任务,那就处于休眠等待
if (first == null)
available.await();
else {
// 获取还需等待的时间
long delay = first.getDelay(NANOSECONDS);
if (delay <= 0)
return q.poll();
first = null; // don't retain ref while waiting
if (leader != null)
available.await();
else {
Thread thisThread = Thread.currentThread();
leader = thisThread;
try {
// 等待相应的时间后才可放行,采取的是Condition的机制
available.awaitNanos(delay);
} finally {
if (leader == thisThread)
leader = null;
}
}
}
}
} finally {
// 唤醒等待的线程或者等待的条件
if (leader == null && q.peek() != null)
available.signal();
lock.unlock();
}
}
上述代码用到了可重入锁以及锁条件Condition的相关知识,后续会详细分析锁的相关知识。此处作下总结
ReentrantLock锁为可重入锁,即相同的线程可再次获取该锁,不必等待阻塞
ReentrantLock的Condition机制,其类似于Object.wait()机制其调用
available.awaitNanos(delay);方法使当前线程休眠指定的时间后,最终会调用q.poll()方法返回待处理的任务有兴趣的读者可自行阅读
ThreadPoolExecutor的runWorker()方法和getTask()方法,便可以彻底理解ScheduledThreadPoolExecutor的定时机制
小结
CocurrentTaskScheduler指定的单线程模型,会让任务按照FIFO的机制有序的被执行,这个模型不大适合多个任务的同时定时执行,会导致任务执行有一定的延迟性。所以建议与spring结合时配置task:scheduler并配置pool-size属性
Spring源码情操陶冶-任务定时器ConcurrentTaskScheduler的更多相关文章
- Spring源码情操陶冶#task:scheduled-tasks解析器
承接前文Spring源码情操陶冶#task:executor解析器,在前文基础上解析我们常用的spring中的定时任务的节点配置.备注:此文建立在spring的4.2.3.RELEASE版本 附例 S ...
- Spring源码情操陶冶-AbstractApplicationContext#finishBeanFactoryInitialization
承接前文Spring源码情操陶冶-AbstractApplicationContext#registerListeners 约定web.xml配置的contextClass为默认值XmlWebAppl ...
- Spring源码情操陶冶-AbstractApplicationContext#registerListeners
承接前文Spring源码情操陶冶-AbstractApplicationContext#onRefresh 约定web.xml配置的contextClass为默认值XmlWebApplicationC ...
- Spring源码情操陶冶-AbstractApplicationContext#onRefresh
承接前文Spring源码情操陶冶-AbstractApplicationContext#initApplicationEventMulticaster 约定web.xml配置的contextClass ...
- Spring源码情操陶冶-AbstractApplicationContext#initApplicationEventMulticaster
承接前文Spring源码情操陶冶-AbstractApplicationContext#initMessageSource 约定web.xml配置的contextClass为默认值XmlWebAppl ...
- Spring源码情操陶冶-AbstractApplicationContext#initMessageSource
承接前文Spring源码情操陶冶-AbstractApplicationContext#registerBeanPostProcessors 约定web.xml配置的contextClass为默认值X ...
- Spring源码情操陶冶-AbstractApplicationContext#registerBeanPostProcessors
承接前文Spring源码情操陶冶-AbstractApplicationContext#invokeBeanFactoryPostProcessors 瞧瞧官方注释 /** * Instantiate ...
- Spring源码情操陶冶-AbstractApplicationContext#invokeBeanFactoryPostProcessors
阅读源码有利于陶冶情操,承接前文Spring源码情操陶冶-AbstractApplicationContext#postProcessBeanFactory 约定:web.xml中配置的context ...
- Spring源码情操陶冶-AbstractApplicationContext#postProcessBeanFactory
阅读源码有利于陶冶情操,承接前文Spring源码情操陶冶-AbstractApplicationContext#prepareBeanFactory 约定:web.xml中配置的contextClas ...
随机推荐
- centos7下更改docker镜像和容器的默认路径
笔者近期在服务器上搭建docker环境,可由于笔者是普通用户,在安装的时候就跳了很多坑,现在记录一下. 一.docker权限问题 据官方解释,搭建docker环境必须使用root权限,或者sudo装, ...
- docker进阶-利用dcoker Swarm搭建简单集群
什么是Swarm 在介绍Swarm之前我们要说一下什么Docker三剑客? Docker-Machine:负责在多种平台上快速安装 Docker 环境. Docker-Compose:Docker ...
- .NET Core快速入门教程 4、使用VS Code开发.NET Core控制台应用程序
一.前言 为什么选择VS Code?VS Code 是一款跨平台的代码编辑器,想想他的哥哥VS,并是微软出品的宇宙第一IDE,那作为VS的弟弟,VS Code 也不会差,毕竟微软出品.反正ken是这么 ...
- java.lnag.Throwable详细解读
public class Throwable extends Object implemnts Serializable Throwable类是所有错误或异常的超类.只有当对象是此类(或其中之 ...
- 一、Python安装与Pycharm使用入门
一.安装Python 1.Linux下安装 一般系统默认已安装2.6.6版本,升级成2.7版本, 但 2.6 不能删除,因为系统对它有依赖,epel源里最新的也是2.6版本,所以以源代码的方式安装2. ...
- js面向对象的理解
ECMAScript 有两种开发模式:1.函数式(过程化),2.面向对象(OOP).面向对象的语言有一个标志,那就是类的概念,而通过类可以创建任意多个具有相同属性和方法的对象.但是,ECMAScrip ...
- 初始配置JDK
什么是java? java是一门编程语言 编程语言有很多种 你比如 C语言 等等 为什么学习java呢! 因为你要和计算机交互 当然了你用汉语跟她说她听不懂 所以你要学习编程语言 那么额咱们的ja ...
- HTTP协议----请求方法和状态码
现在广泛使用的是HTTP/1.1版本,发布于1997年. 理解HTTP协议,首先从请求开始,比如: POST /form/entry HTTP/1.1 格式为: 请求方法 URI 协议版本 请求方法: ...
- C#触发器知识总结及案例
触发器 触发器是在对表进行插入.更新.删除操作时自动执行的存储过程,常用于强制业务规则,是一种高级约束,可以定义比用check约束更为复杂的约束.可以执行复杂的SQL语句(if/while/case) ...
- MyGod--Beta版本前期报告
下一阶段需要改进完善的功能 1.完善购买功能,商品购买后,将生成申请订单,卖家将收到提醒.卖家在完成订单后,可以选择完成订单,商品将下架. 2.完善搜索功能,将界面中的搜索功能添加进去(简单考虑只搜索 ...