采用ScheduledThreadPoolExecutor执行定时重试任务时内存溢出的分析及解决
采用ScheduledThreadPoolExecutor执行定时重试任务时内存溢出的分析及解决

采用ScheduledThreadPoolExecutor执行定时重试任务时内存溢出的分析及解决作者:余志坚姜春志来源:《科技资讯》2016年第07期摘要:开发JavaWeb项目中发现服务之间的调用存在超时情况,由于涉及的处理逻辑全部是异步,引入定时重试的机制,重试工具选择了JDK自带的ScheduledThreadPoolExecutor。
当A服务依赖B服务,B服务由于在业务高峰期处理能力降低,导致大量A服务过来的请求超时,A加入了超时重试机制,间隔时间根据重试次数的多少来决定,次数越多,两次重试之间间隔的时间越多,此时的业务高峰也会给A带来大量请求,大量的超时会导致重试队列迅速堆积,直到内存溢出。
该文从线程池工作机制、ScheduledThreadPoolExecutor实例的创建,获取重试任务的过程以及提交任务的过程角度分析,并通过源代码的剖析和测试工具MyEclipse进行演示测试内存泄露的情况,得出避免内存泄露的解决方案。
关键词:ScheduledThreadPoolExecutor 线程池内存溢出中图分类号:TP3 文献标识码:A 文章编号:1672-3791(2016)03(a)-0015-031 ScheduledThreadPoolExecutor实例的创建过程及线程池工作机制1.1 ScheduledThreadPoolExecutor实例的创建过程重试工具选择了JDK自带的ScheduledThreadPoolExecutor。
ScheduledThreadPoolExecutor 实例的创建过程如下:ScheduledThreadPoolExecutor实例的创建过程如下:(1)获取当前机器上处理器的数量;(2)使用Google的ThreadFactoryBuiler创建指定格式名称的线程,以方便查看问题;(3)有需要被拒绝的任务时,抛出异常;(4)创建定时任务池;打开MyEclipse 工具显示相对的代码:int corePoolSize=Runtime.getRuntime().availableProcessors();ThreadFactory tf=new ThreadFactoryBuilder().setNameFormat("FailureRetryTask-pool-%d").build();RejectedExecutionHandler handler=new ThreadPoolExecutor.AbortPolicy();ScheduledThreadPoolExecutor taskService=new ScheduletThreadPooExecutor(corePoolSize,tf,handler);线程池就是多个线程在一个队列中取任务执行,提交的任务会被放入队列中等待线程执行,故队列要设置一个大小。
Springboot定时任务Scheduled重复执行操作

Springboot定时任务Scheduled重复执⾏操作今天⽤scheduled写定时任务的时候发现定时任务⼀秒重复执⾏⼀次,⽽我的cron表达式为 * 0/2 * * * * 。
在源码调试的过程中,发现是我的定时任务执⾏过程太短导致的。
于是我另外写了个简单的定时任务@Componentpublic class TestJob {@Scheduled(cron = "* 0/2 * * * *")public void test() {System.out.println("测试开始");System.out.println("测试结束");}}上述任务在启动之后⼀直执⾏。
然后我在任务后⾯加⼊线程睡眠1分钟。
@Componentpublic class TestJob {@Scheduled(cron = "* 0/2 * * * *")public void test() {System.out.println("测试开始");System.out.println("测试结束");try {Thread.sleep(60000);} catch (InterruptedException e) {e.printStackTrace();}System.out.println("睡眠结束");}}上述任务执⾏⼀次就没有再执⾏了。
所以我继续深⼊查看源码,发现问题在于CronSequenceGenerator.class的next⽅法。
public Date next(Date date) {Calendar calendar = new GregorianCalendar();calendar.setTimeZone(this.timeZone);calendar.setTime(date);//1.设置下次执⾏时间的毫秒为0,如上次任务执⾏过程不⾜1秒,则calendar的时间会被设置成上次任务的执⾏时间calendar.set(14, 0);long originalTimestamp = calendar.getTimeInMillis();this.doNext(calendar, calendar.get(1));//2.由于有上⾯⼀步,执⾏时间太短,会导致下述条件为trueif(calendar.getTimeInMillis() == originalTimestamp) {//3.calendar在原来的时间上增加1秒calendar.add(13, 1);//CronSequenceGenerator的doNext算法从指定时间开始(包括指定时间)查找符合cron表达式规则下⼀个匹配的时间//注意第⼀个匹配符是*,由于增加了1秒,依然符合cron="* 0/2 * * * *",所以下⼀个执⾏时间就是在原来的基础上增加了⼀秒this.doNext(calendar, calendar.get(1));}return calendar.getTime();}请查看代码中的注释,由于任务执⾏时间太短了,代码会进⼊if语句,并设置执⾏时间在原来的基础上增加⼀秒。
Java调度线程池ScheduledThreadPoolExecutor源码分析

Java调度线程池ScheduledThreadPoolExecutor源码分析最近新接⼿的项⽬⾥⼤量使⽤了ScheduledThreadPoolExecutor类去执⾏⼀些定时任务,之前⼀直没有机会研究这个类的源码,这次趁着机会好好研读⼀下。
原⽂地址:该类主要还是基于ThreadPoolExecutor类进⾏⼆次开发,所以对Java线程池执⾏过程还不了解的同学建议先看看我之前的⽂章。
⼀、执⾏流程1. 与ThreadPoolExecutor不同,向ScheduledThreadPoolExecutor中提交任务的时候,任务被包装成ScheduledFutureTask对象加⼊延迟队列并启动⼀个woker线程。
2. ⽤户提交的任务加⼊延迟队列时,会按照执⾏时间进⾏排列,也就是说队列头的任务是需要最早执⾏的。
⽽woker线程会从延迟队列中获取任务,如果已经到了任务的执⾏时间,则开始执⾏。
否则阻塞等待剩余延迟时间后再尝试获取任务。
3. 任务执⾏完成以后,如果该任务是⼀个需要周期性反复执⾏的任务,则计算好下次执⾏的时间后会重新加⼊到延迟队列中。
⼆、源码深⼊分析⾸先看下ScheduledThreadPoolExecutor类的⼏个构造函数:public ScheduledThreadPoolExecutor(int corePoolSize) {super(corePoolSize, Integer.MAX_VALUE, 0, NANOSECONDS,new DelayedWorkQueue());}public ScheduledThreadPoolExecutor(int corePoolSize,ThreadFactory threadFactory) {super(corePoolSize, Integer.MAX_VALUE, 0, NANOSECONDS,new DelayedWorkQueue(), threadFactory);}public ScheduledThreadPoolExecutor(int corePoolSize,RejectedExecutionHandler handler) {super(corePoolSize, Integer.MAX_VALUE, 0, NANOSECONDS,new DelayedWorkQueue(), handler);}public ScheduledThreadPoolExecutor(int corePoolSize,ThreadFactory threadFactory,RejectedExecutionHandler handler) {super(corePoolSize, Integer.MAX_VALUE, 0, NANOSECONDS,new DelayedWorkQueue(), threadFactory, handler);}注:这⾥构造函数都是使⽤super,其实就是ThreadPoolExecutor的构造函数这⾥有三点需要注意:1. 使⽤DelayedWorkQueue作为阻塞队列,并没有像ThreadPoolExecutor类⼀样开放给⽤户进⾏⾃定义设置。
java scheduledthreadpoolexecutor cron表达式 -回复

java scheduledthreadpoolexecutor cron表达式-回复Java中的ScheduledThreadPoolExecutor是一个用于调度任务的线程池,它允许根据cron表达式指定任务的执行时间。
本文将详细介绍cron表达式的语法和使用方法,并且提供一些实际示例来帮助读者更好地理解和应用cron表达式。
# 1. 什么是cron表达式cron表达式是一种用于指定任务执行时间的时间表达式,它有6个或7个字段组成,每个字段代表时间的一个部分。
这些字段的排列顺序分别是:秒(0-59)、分(0-59)、小时(0-23)、日期(1-31)、月份(1-12)、星期(0-7,其中0和7表示星期天)和年份(可选)。
# 2. cron表达式的语法cron表达式的语法由各个字段的取值规定,下面是cron表达式的基本语法:1 2 3 4 5 6 7字段的取值规则如下:- 字段1(秒):0-59的整数- 字段2(分):0-59的整数- 字段3(小时):0-23的整数- 字段4(日期):1-31的整数(有些特殊的月份日期可能无效)- 字段5(月份):1-12的整数- 字段6(星期):0-7的整数或者英文缩写(0和7表示星期天,1表示星期一,以此类推)- 字段7(年份,可选):1970-2099的整数# 3. cron表达式的使用示例现在我们来看一些具体的cron表达式示例,以帮助读者更好地理解和使用cron表达式。
示例1:每分钟执行一次* * * * * *这个cron表达式表示每分钟执行一次。
示例2:每天的10点和22点执行一次0 0 10,22 * * *这个cron表达式表示在每天的10点和22点都会执行一次。
示例3:在每月的第2个星期一的上午10点执行一次0 0 10 ? * 2#2 *这个cron表达式表示在每月的第2个星期一的上午10点执行一次。
# 4. 使用ScheduledThreadPoolExecutor执行任务在Java中,我们可以使用ScheduledThreadPoolExecutor来按照cron 表达式执行任务。
java定时器线程池(ScheduledThreadPoolExecutor)的实现

java定时器线程池(ScheduledThreadPoolExecutor)的实现前⾔定时器线程池提供了定时执⾏任务的能⼒,即可以延迟执⾏,可以周期性执⾏。
但定时器线程池也还是线程池,最底层实现还是ThreadPoolExecutor,可以参考我的另外⼀篇⽂章多线程–精通ThreadPoolExecutor。
特点说明1.构造函数public ScheduledThreadPoolExecutor(int corePoolSize) {// 对于其他⼏个参数在ThreadPoolExecutor中都已经详细分析过了,所以这⾥,将不再展开// 这⾥我们可以看到调⽤基类中的⽅法时有个特殊的⼊参DelayedWorkQueue。
// 同时我们也可以发现这⾥并没有设置延迟时间、周期等参数⼊⼝。
// 所以定时执⾏的实现必然在DelayedWorkQueue这个对象中了。
super(corePoolSize, Integer.MAX_VALUE, 0, NANOSECONDS,new DelayedWorkQueue());}2.DelayedWorkQueueDelayedWorkQueue是在ScheduledThreadPoolExecutor的⼀个内部类,实现了BlockingQueue接⼝⾥⾯存放任务队列的数组如下:private RunnableScheduledFuture<?>[] queue =new RunnableScheduledFuture<?>[INITIAL_CAPACITY];我们分析过ThreadPoolExecutor,它从任务队列中获取任务的⽅式为poll和take两种,所以看⼀下poll和take两个⽅法的源码,回顾⼀下,ThreadPoolExecutor它会调⽤poll或take⽅法,先poll,再take,只要其中⼀个接⼝有返回就⾏public RunnableScheduledFuture<?> poll() {final ReentrantLock lock = this.lock;lock.lock();try {RunnableScheduledFuture<?> first = queue[0];// 这⾥有个getDelay,这是关键点,获取执⾏延时时间// 但是如果我们有延时设置的话,这就返回空了,然后就会调⽤take⽅法if (first == null || first.getDelay(NANOSECONDS) > 0)return null;elsereturn finishPoll(first);} finally {lock.unlock();}}public RunnableScheduledFuture<?> take() throws InterruptedException {final ReentrantLock lock = this.lock;lock.lockInterruptibly();try {for (;;) {RunnableScheduledFuture<?> first = queue[0];if (first == null)available.await();else {// 获取延时时间long delay = first.getDelay(NANOSECONDS);if (delay <= 0)return finishPoll(first);first = null; // don't retain ref while waitingif (leader != null)available.await();else {Thread thisThread = Thread.currentThread();leader = thisThread;try {// 使⽤锁,执⾏延时等待。
newscheduledthreadpool的使用 -回复

newscheduledthreadpool的使用-回复Newscheduledthreadpool是Java中的一个线程池,它提供了一种方便的方式来管理和执行多线程任务。
在本文中,我将逐步解释此线程池的使用,并提供示例代码和最佳实践建议。
一、什么是线程池在介绍Newscheduledthreadpool之前,我们首先需要了解什么是线程池。
在编程中,我们经常需要并发执行多个任务,而每个任务可能都需要创建一个新的线程来执行。
然而,频繁地创建和销毁线程是非常耗费资源的,因为线程的创建和销毁本身就是一个昂贵的操作。
线程池可以帮助我们解决这个问题,它允许我们预先创建一组线程并重用它们来执行多个任务。
二、Newscheduledthreadpool的作用Newscheduledthreadpool是Java中的一个线程池实现,它基于ScheduledExecutorService接口,并允许我们按照预定的时间间隔执行任务。
ScheduledExecutorService是一个接口,它继承了ExecutorService并添加了一些执行周期性任务的方法。
Newscheduledthreadpool的主要作用是在指定的时间间隔内周期性地执行任务。
它适用于需要按照一定的时间间隔重复执行任务的场景,比如定时任务、定时数据采集等。
三、如何使用Newscheduledthreadpool使用Newscheduledthreadpool非常简单。
我们只需要按照以下步骤进行操作:步骤一:创建一个ScheduledExecutorService实例可以使用Executors类提供的静态方法来创建一个ScheduledExecutorService实例,例如:ScheduledExecutorService scheduledExecutorService = Executors.newScheduledThreadPool(5);上面的代码创建了一个拥有5个线程的ScheduledExecutorService实例。
采用ScheduledThreadPoolExecutor执行定时重试任务时内存溢出的分析及解决

科技资讯2016 NO.07SCIENCE & TECHNOLOGY INFORMATION信 息 技 术15科技资讯 SCIENCE & TECHNOLOGY INFORMATION 1 ScheduledThreadPoolExecutor 实例的创建过程及线程池工作机制1.1 ScheduledThreadPoolExecutor实例的创建过程重试工具选择了JDK自带的ScheduledThreadPoolExecutor。
ScheduledThreadPoolExecutor实例的创建过程如下:ScheduledThreadPoolExecutor实例的创建过程如下:(1)获取当前机器上处理器的数量;(2)使用Google的ThreadFactoryBuiler 创建指定格式名称的线程,以方便查看问题;(3)有需要被拒绝的任务时,抛出异常;(4)创建定时任务池;打开MyEclipse工具显示相对的代码:int corePoolSize=Runtime.getRuntime().availableProcessors();ThreadFactory tf=new ThreadFactoryBuilder().setNameFormat("FailureRetryTask-pool-%d").build();R e j e c t e d E x e c u t i o n H a n d l e r h a n d l e r =n e w ThreadPoolExecutor.AbortPolicy();ScheduledThreadPoolExecutor taskService=new ScheduletThreadPooExecutor(corePoolSize,tf,handler);线程池就是多个线程在一个队列中取任务执行,提交的任务会被放入队列中等待线程执行,故队列要设置一个大小。
Java定时任务ScheduledThreadPoolExecutor

Java定时任务ScheduledThreadPoolExecutorTimer计时器有管理任务延迟执行("如1000ms后执行任务")以及周期性执行("如每500ms执行一次该任务")。
但是,Timer存在一些缺陷,因此你应该考虑使用ScheduledThreadPoolExecutor作为代替品,Timer对调度的支持是基于绝对时间,而不是相对时间的,由此任务对系统时钟的改变是敏感的;ScheduledThreadExecutor只支持相对时间。
Timer的另一个问题在于,如果TimerTask抛出未检查的异常,Timer将会产生无法预料的行为。
Timer线程并不捕获异常,所以TimerTask抛出的未检查的异常会终止timer 线程。
这种情况下,Timer也不会再重新恢复线程的执行了;它错误的认为整个Timer都被取消了。
此时,已经被安排但尚未执行的TimerTask永远不会再执行了,新的任务也不能被调度了。
例子:packagecom.concurrent.basic;importjava.util.Timer;import java.util.TimerTask;public class TimerT est {private Timer timer = new Timer();// 启动计时器public void lanuchTimer() {timer.schedule(new TimerTask() {public void run() {throw new RuntimeException();}}, 1000 * 3, 500);}// 向计时器添加一个任务public void addOneTask() {timer.schedule(new TimerTask() {public void run() {System.out.println("hello world");}}, 1000 * 1, 1000 * 5);}public static void main(String[] args) throws Exception {TimerT est test = new TimerT est();/doc/1813516196.html,nuchTimer();Thread.sleep(1000 * 5);// 5秒钟之后添加一个新任务test.addOneTask();}}执行结果:你可能希望第二个没有异常的线程会一直运行下去,然而实际情况如程序所示5秒钟后就中止了,还伴随着一个异常,异常的消息是"Timer already cancelled"。
- 1、下载文档前请自行甄别文档内容的完整性,平台不提供额外的编辑、内容补充、找答案等附加服务。
- 2、"仅部分预览"的文档,不可在线预览部分如存在完整性等问题,可反馈申请退款(可完整预览的文档不适用该条件!)。
- 3、如文档侵犯您的权益,请联系客服反馈,我们会尽快为您处理(人工客服工作时间:9:00-18:30)。
采用ScheduledThreadPoolExecutor执行定时重试任务时内存溢出的分析及解决作者:余志坚姜春志来源:《科技资讯》2016年第07期摘要:开发JavaWeb项目中发现服务之间的调用存在超时情况,由于涉及的处理逻辑全部是异步,引入定时重试的机制,重试工具选择了JDK自带的ScheduledThreadPoolExecutor。
当A服务依赖B服务,B服务由于在业务高峰期处理能力降低,导致大量A服务过来的请求超时,A加入了超时重试机制,间隔时间根据重试次数的多少来决定,次数越多,两次重试之间间隔的时间越多,此时的业务高峰也会给A带来大量请求,大量的超时会导致重试队列迅速堆积,直到内存溢出。
该文从线程池工作机制、ScheduledThreadPoolExecutor实例的创建,获取重试任务的过程以及提交任务的过程角度分析,并通过源代码的剖析和测试工具MyEclipse进行演示测试内存泄露的情况,得出避免内存泄露的解决方案。
关键词:ScheduledThreadPoolExecutor 线程池内存溢出中图分类号:TP3 文献标识码:A 文章编号:1672-3791(2016)03(a)-0015-031 ScheduledThreadPoolExecutor实例的创建过程及线程池工作机制1.1 ScheduledThreadPoolExecutor实例的创建过程重试工具选择了JDK自带的ScheduledThreadPoolExecutor。
ScheduledThreadPoolExecutor 实例的创建过程如下:ScheduledThreadPoolExecutor实例的创建过程如下:(1)获取当前机器上处理器的数量;(2)使用Google的ThreadFactoryBuiler创建指定格式名称的线程,以方便查看问题;(3)有需要被拒绝的任务时,抛出异常;(4)创建定时任务池;打开MyEclipse 工具显示相对的代码:int corePoolSize=Runtime.getRuntime().availableProcessors();ThreadFactory tf=new ThreadFactoryBuilder().setNameFormat("FailureRetryTask-pool-%d").build();RejectedExecutionHandler handler=new ThreadPoolExecutor.AbortPolicy();ScheduledThreadPoolExecutor taskService=new ScheduletThreadPooExecutor(corePoolSize,tf,handler);线程池就是多个线程在一个队列中取任务执行,提交的任务会被放入队列中等待线程执行,故队列要设置一个大小。
线程池同样会根据任务繁忙程度来动态调整连接数,空闲时保持最小连接数,繁忙时增加连接,但不会超过上限,具有伸缩性,线程的创建和销毁也需要消耗系统资源,线程的连接重用就可以避免这部分损失,具有重用性。
1.2 线程池工作机制线程获取任务的策略就是如果当前线程池运行状态正常,则阻塞等待任务,否则直接返回或等待有限时间后返回。
线程池中线程的主要任务就是获取任务,然后执行,然后再去获取任务,如此循环,这就实现了线程池中线程的可重用。
Worker封装了任务,同时创建了新的线程,并被添加到集合workers中,这个workers其实就是最核心的线程池。
通过run方法实现重用。
private final HashSet workers=new HashSet ();public void run(){try{Runnable task=firstTask;firstTask=null;while(task!=null||(task=getTask())!=null){runTask(task);task=null;}}finally{workerDone(this);}}Runnable getTask(){for(;;){try{int state=runState;if(state>SHUTDOWN){return null;}Runnable r;if(state==SHUTDOWN){r=workQueue.poll();}else if(poolSize>corePoolSize||allowCoreThreadTimeOut){r=workQueue.poll(keepAliveTime,TimeUnit.NANOSECONDS);}else{r=workQueue.take();}if(r!=null){return r;}if(workerCanExit()){if(runState>=SHUTDOWN){interruptIdleWorkers();}return null;}}catch(InterruptedException ie){}}}private boolean workerCanExit(){final RenntrantLock mainLock=this.mainLock;mainLock.lock();boolean canExit; try{canExit=runState>=STOP||workQueue.isEmpty()||(allowCoreThreadTimeOut&&poolSize>Math.max(1,corePoolSize));}finally{mainLock.unLock();}return canExit;如果此时线程池运行状态是终止(runState >= STOP),或者队列为空,或者允许核心线程超时并且线程池中线程数量大于最小线程数量,那么方法将返回true。
再回到getTask方法,调用workerCanExit方法的前提是没有获取到任务,根据上边获取任务的过程,这几个条件都有可能成立,所以此时getTask方法可以返回null,上层Worker的run方法从while循环重返回,整个线程结束,这就实现了线程池的可伸缩。
2 ScheduledThreadPoolExecutor获取任务的过程在getTask()中,描述了整个获取任务的过程,如果线程池运行状态已经是SHUTDOWN了,调用非阻塞方法poll,因为如果当前有任务,那么可以获取到任务并返回,如果没有任务,也没有必要阻塞在队列上等待任务,因为已经SHUTDOWN,后续不会再有任务进入。
如果当前线程数大于最小线程数,或者核心线程也可以做超时处理,意味着如果获取不到任务就可以销毁一部分线程了,所以poll方法设置了等待时间,超时后立即返回。
另一种情况是线程池还在运行状态,并且当前线程数不大于最小线程数,同时也不允许最小线程数以内的线程超时,这个时候线程就要调用阻塞方法take,等待任务进入队列以后才返回。
3 ScheduledThreadPoolExecutor提交任务的执行过程ScheduledThreadPoolExecutor提交任务的执行过程,首先提交任务:taskService .schedule (new Runnable(){public void run(){}},1,TimeUnit,DAYS);ScheduledThreadPoolExecutor通过schedule方法提交定时任务,schedule方法源码如下:public ScheduledFuture schedule(Runnable command,long delay,TimeUnit unit){if(command==null||unit==null){throw new NullPointerException();}if(delayRunnableScheduledFuture t=decorateTask(command,new ScheduledFutureTask (command,null,triggerTime));delayedExecute(t);return t;提交的任务会被封装成ScheduledFutureTask类型对象。
分析delayedExecute方法:private void delayedExecute(Runnable command){if(isShutDown()){reject(command);return;}if(getPoolSize()super.getQueue().add(command);}如果线程的运行状态不是RUNNNING或者入队列没有成功,则采用线程池的构造方法中设置的拒绝策略来处理任务。
如果当前线程池中的线程数量poolSize小于线程池核心线程的数量corePoolSize,执行prestartCoreThread(),该方法会创建一个新的线程来执行任务,如果prestartCoreThread()创建的新线程执行任务失败或者当前线程池中的线程数量poolSize大于等于线程池核心线程数量corePoolSize,当若线程池的运行状态是RUNNING并且入队成功,由于在多线程环境下,状态随时可能会改变,此时线程池的运行状态runState不是RUNNING或者线程池中没有可用的线程(poolSize==0),要确保进入的任务被执行处理,线程池在初始化完成以后是空的,并没有线程,如果在服务器中使用线程池,服务重启后有大量请求进入,则要同时创建多个线程,而且创建过程是加锁同步的,会导致一定的竞争,解决办法就是线程池初始化时调用prestartAllCoreThreads方法启动核心线程数量的线程,这样就能在线程池中的线程就绪以后才开始接收请求。
通过getQueue方法获取任务队列,并且调用add方法向队列中添加任务,dq的定义:private final DelayQueue dq=new DelayQueue();public boolean add(Runnable x){return dq.add((RunnableScheduleFuture)x);}可以看出dq是阻塞队列,线程池中的线程都是在队列中取数据,ScheduledThreadPoolExecutor中的构造方法里的队列的实现使用链表结构的阻塞队列,add方法内部调用offer方法,offer源码如下:public boolean offer(E e){final ReentrantLock lock=this.lock();lock.lock();E first=q.peek();q.offer(e);if(first==null||pareTo(first)available.singalAll();return true; }}finally{lock.unlock();}}这方法需要在多线程环境下同步执行,会用到锁Lock。