• spring scheduled单线程和多线程使用过程中的大坑!!不看到时候绝对后悔!!


    公司在使用定时任务的时候,使用的是spring scheduled。
    代码如下:

    @EnableScheduling
    public class TaskFileScheduleService {


    @Scheduled(cron="0 */1 * * * ?")
    public void task1(){
    .......
    }

    @Scheduled(cron="0 */1 * * * ?")
    public void task2(){
    .......

    某天,接到领导的电话,说生产环境的定时任务不跑了,赶紧给看看~
    做为一名负责人的程序员,赶紧放下手中泡面,远程到公司的电脑~
    线程卡死这种问题,第一步当然是将jvm中的heap dump和thread dump导出来~
    经过简单分析,thread dump中某个线程确实一直处理running状态,heap dump没啥问题~
    thread dump中的问题线程:

    "pool-2-thread-43" #368 prio=5 os_prio=0 tid=0x00005587fd54c800 nid=0x1df runnable [0x00007ff7e2056000]
    java.lang.Thread.State: RUNNABLE
    at java.net.SocketInputStream.socketRead0(Native Method)
    at java.net.SocketInputStream.socketRead(SocketInputStream.java:116)
    at java.net.SocketInputStream.read(SocketInputStream.java:171)
    at java.net.SocketInputStream.read(SocketInputStream.java:141)
    at java.net.SocketInputStream.read(SocketInputStream.java:224)
    at ch.ethz.ssh2.transport.ClientServerHello.readLineRN(ClientServerHello.java:30)
    at ch.ethz.ssh2.transport.ClientServerHello.<init>(ClientServerHello.java:67)
    at ch.ethz.ssh2.transport.TransportManager.initialize(TransportManager.java:455)
    at ch.ethz.ssh2.Connection.connect(Connection.java:643)
    - locked <0x000000074539e0e8> (a ch.ethz.ssh2.Connection)
    at ch.ethz.ssh2.Connection.connect(Connection.java:490)
    - locked <0x000000074539e0e8> (a ch.ethz.ssh2.Connection)
    at com.suneee.yige.medicalserver.common.SSHUtils.connect(SSHUtils.java:24)
    at com.suneee.yige.medicalserver.service.TaskFileScheduleService.getConn(TaskFileScheduleService.java:102)
    at com.suneee.yige.medicalserver.service.TaskFileScheduleService.taskInfo(TaskFileScheduleService.java:108)
    at com.suneee.yige.medicalserver.service.TaskFileScheduleService.task(TaskFileScheduleService.java:74)
    at sun.reflect.GeneratedMethodAccessor295.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:81)
    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) 
    很明显,ch.ethz.ssh2.Connection.connect这个方法卡死,导致线程一直处于running状态。

    由于spring scheduled默认是所有定时任务都在一个线程中执行!!这是个大坑!!!
    也就是说定时任务1一直在执行,定时任务2一直在等待定时任务1执行完成。这就导致了生产上定时任务全部卡死的现象。

    问题已经很明确了,要么解决ch.ethz.ssh2.Connection.connect卡死的问题,要么解决spring scheduled单线程处理的问题。

    首先,想到的是处理ch.ethz.ssh2.Connection.connect卡死的问题,但是经过一番查找,发现这个ssh的工具包很久没更更新过了,也没有设置例如httpclient的超时时间之类的。这就很难办了!果断放弃!!

    现在只剩一条路,怎么在任务1卡死的时候,任务2可以按他自己的周期执行,且任务1也按照固定周期执行,不会因为某次任务1卡死导致后续的定时任务出现问题!

    方法一:
    添加配置

    @Configuration
    public class ScheduleConfig implements SchedulingConfigurer {
    @Override
    public void configureTasks(ScheduledTaskRegistrar taskRegistrar) {
    taskRegistrar.setScheduler(Executors.newScheduledThreadPool(50));
    }

    这个方法,在程序启动后,会逐步启动50个线程,放在线程池中。每个定时任务会占用1个线程。但是相同的定时任务,执行的时候,还是在同一个线程中。
    例如,程序启动,每个定时任务占用一个线程。任务1开始执行,任务2也开始执行。如果任务1卡死了,那么下个周期,任务1还是处理卡死状态,任务2可以正常执行。也就是说,任务1某一次卡死了,不会影响其他线程,但是他自己本身这个定时任务会一直等待上一次任务执行完成!
    这种显然不行!这也是踩过坑才知道的!!!

    方法二(正解):

    添加配置:

    @Configuration
    @EnableAsync
    public class ScheduleConfig {

    @Bean
    public TaskScheduler taskScheduler() {
    ThreadPoolTaskScheduler taskScheduler = new ThreadPoolTaskScheduler();
    taskScheduler.setPoolSize(50);
    return taskScheduler;
    }

    在方法上添加注解@Async

    @EnableScheduling
    public class TaskFileScheduleService {


    @Async
    @Scheduled(cron="0 */1 * * * ?")
    public void task1(){
    .......
    }

    @Async
    @Scheduled(cron="0 */1 * * * ?")
    public void task2(){
    .......

    这种方法,每次定时任务启动的时候,都会创建一个单独的线程来处理。也就是说同一个定时任务也会启动多个线程处理。
    例如:任务1和任务2一起处理,但是线程1卡死了,任务2是可以正常执行的。且下个周期,任务1还是会正常执行,不会因为上一次卡死了,影响任务1。
    但是任务1中的卡死线程越来越多,会导致50个线程池占满,还是会影响到定时任务。
    这时候,可能会几个月发生一次~到时候再重启就行了!


    ————————————————
    版权声明:本文为CSDN博主「程序员大佬」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。
    原文链接:https://blog.csdn.net/FlyingSnails/article/details/90167434

  • 相关阅读:
    【Nginx】开启 gzip和缓存
    webpack分离css单独打包
    【转】为什么Github没有记录你的Contributions
    Swiper使用遇到的问题
    Jenkins 自动化构建
    Pre标签 自动换行
    Gulp入门教程
    计数排序
    直接插入排序
    等差素数列
  • 原文地址:https://www.cnblogs.com/javalinux/p/14845114.html
Copyright © 2020-2023  润新知