1,今天研究了一下Spring中的@Scheduled注解实现的定时任务
- 首先如果只使用了@Scheduled的话,现在程序会同步执行定时方法
- 如果同时使用了@Async("线程池的bean")注解以及@EnableAsync,则会将将定时方法提交到线程池异步执行。
首先我通过Spring创建了线程池java.util.concurrent.ThreadPoolExecutor@56ab55a8,
大小为[Running, pool size = 200, active threads = 200, queued tasks = 10, completed tasks = 0]
ps:@56ab55a8用来表示jvm内存中的对象,用的是8位16进制整数来表示,一共可以表示4294967296个对象,想一下我们一个项目会创建很多个对象,不计其数的对象,4294967296个够么,现在我们来计算一下,假设一个对象有1kb,并不大。
4294967296 * 1kb = 4194304MB = 4096GB,试想一下内存有4TB的机器是什么机器,所以说当我们创建的对象没有达到4294967296个时就已经OOM了,所有4294967296足够用来给所有对象编号。
如果现在发生了一个特殊情况,现在定时任务频次很快,但是执行一次需要的时间很长,线程池中200个线程都使用完了,目前没有空余线程,但是还有新的任务进来,线程池的10个排队队列也已经用完了,此时会抛出异常。已使用线程如下图
org.springframework.core.task.TaskRejectedException: Executor [java.util.concurrent.ThreadPoolExecutor@56ab55a8[Running, pool size = 200, active threads = 200, queued tasks = 10, completed tasks = 0]] did not accept task: org.springframework.aop.interceptor.AsyncExecutionInterceptor$$Lambda$515/141212390@6895804c
at org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor.submit(ThreadPoolTaskExecutor.java:336)
at org.springframework.aop.interceptor.AsyncExecutionAspectSupport.doSubmit(AsyncExecutionAspectSupport.java:284)
at org.springframework.aop.interceptor.AsyncExecutionInterceptor.invoke(AsyncExecutionInterceptor.java:129)
at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:185)
at org.springframework.aop.framework.CglibAopProxy$DynamicAdvisedInterceptor.intercept(CglibAopProxy.java:688)
at com.zjx.scms.task.ParsingCSVTask$$EnhancerBySpringCGLIB$$ded34dcf.resetWalletCount1(<generated>)
at sun.reflect.GeneratedMethodAccessor103.invoke(Unknown Source)
at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
at java.lang.reflect.Method.invoke(Method.java:498)
at org.springframework.scheduling.support.ScheduledMethodRunnable.run(ScheduledMethodRunnable.java:65)
at org.springframework.scheduling.support.DelegatingErrorHandlingRunnable.run(DelegatingErrorHandlingRunnable.java:54)
at org.springframework.scheduling.concurrent.ReschedulingRunnable.run(ReschedulingRunnable.java:93)
at java.util.concurrent.Executors$RunnableAdapter.call(Executors.java:511)
at java.util.concurrent.FutureTask.run(FutureTask.java:266)
at java.util.concurrent.ScheduledThreadPoolExecutor$ScheduledFutureTask.access$201(ScheduledThreadPoolExecutor.java:180)
at java.util.concurrent.ScheduledThreadPoolExecutor$ScheduledFutureTask.run(ScheduledThreadPoolExecutor.java:293)
at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1149)
at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:624)
at java.lang.Thread.run(Thread.java:748)
抛出TaskRejectedException(任务拒绝Exception),从异常中可以看出,Spring对定时方法采用了CglibAopProxy动态代理,然后进行线程池submit。
总结
由此我们可以看出,在给定时任务配置线程池的时候,应该大概评估定时任务需要的资源情况来更好的设置线程池策略。
Question
- 问: 每个@Scheduled都拥有一个线程池,还是所有的@Scheduled都使用同一个线程池。
- 答: 得看@Async配置了哪个线程池,不使用@Async时,是同步执行任务