@Scheduled(cron = "* * * * * ?")实现原理

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时,是同步执行任务
©著作权归作者所有,转载或内容合作请联系作者
平台声明:文章内容(如有图片或视频亦包括在内)由作者上传并发布,文章内容仅代表作者本人观点,简书系信息发布平台,仅提供信息存储服务。

推荐阅读更多精彩内容