• 记一次使用Spring注解@Scheduled出现的事故


    问题

    老板要求小省同学开发一个下班后提醒员工吃饭提醒的功能,谁料功能开发完成上线后,员工分分投诉说功能有问题,食堂都放饭一小时了,才发提醒到员工的手机上。这让身为干饭人的小省同学立刻意识到了问题的严重性,于是有了接下来的故事

    原因

    上代码

     @Scheduled(cron = "0/1 * * * * ? ")
     public void punch() throws InterruptedException {
         System.out.println("下班打卡提醒:" + DateUtils.dateTimeNow());
         //模拟打卡提醒业务流程处理 如果延时一小时
         Thread.sleep(1000 * 3);
     }
    
     @Scheduled(cron = "0/1 * * * * ? ")
     public void eat() {
         System.out.println("食堂开饭提醒:" + DateUtils.dateTimeNow());
     }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11

    通过代码发现,除了有员工吃饭的提醒定时任务,还有其他的定时任务也会执行,盲猜一波是其它定时任务影响到了吃饭提醒

    下班打卡提醒:20220726182219
    食堂开饭提醒:20220726182222
    下班打卡提醒:20220726182223
    食堂开饭提醒:20220726182226
    下班打卡提醒:20220726182227
    食堂开饭提醒:20220726182230
    下班打卡提醒:20220726182231
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7

    代码运行代码发现,只要下班打卡执行后,食堂开饭就会出现延时,那么直接去掉下班打卡提醒不就OK了吗

    食堂开饭提醒:20220726182511
    食堂开饭提醒:20220726182512
    食堂开饭提醒:20220726182513
    食堂开饭提醒:20220726182514
    食堂开饭提醒:20220726182515
    食堂开饭提醒:20220726182516
    食堂开饭提醒:20220726182517
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7

    去掉打卡提醒后,就完全正常了,好了 下班了在这里插入图片描述

    被老板狠狠的打了一顿后,发现下班打卡存在还是很有必要的

    为什么会影响呢
    老规划,打断点,查看源码,通过调用链排查到如下代码

    @Override
    public void run() {
    	try {
    		this.delegate.run();
    	}
    	catch (UndeclaredThrowableException ex) {
    		this.errorHandler.handleError(ex.getUndeclaredThrowable());
    	}
    	catch (Throwable ex) {
    		this.errorHandler.handleError(ex);
    	}
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12

    在这里插入图片描述看到这里真相就水落石出了。
    线程池中里面只有一个活跃线程在执行,那么在单线程的情况下,吃饭提醒就被其它定时任务阻塞了,所以出现了延时提醒

    解决

    既然是因为单线程出现的问题,那么改成多线程或异步应该能解决问题了

    方案一

    先看一波官网咋搞

    @Async
    void doSomething() {
        // this will be run asynchronously
    }
    
    • 1
    • 2
    • 3
    • 4

    加异步注解,确实可行,亲测可用
    官网参考:
    https://docs.spring.io/spring-framework/docs/current/reference/html/integration.html#scheduling-annotation-support-async

    方案二

    业务代码异步执行

    public ThreadPoolTaskExecutor getAsyncExecutor() {
           ThreadPoolTaskExecutor taskExecutor = new ThreadPoolTaskExecutor();
           taskExecutor.setCorePoolSize(3);
           taskExecutor.setMaxPoolSize(5);
           taskExecutor.setQueueCapacity(200);
           taskExecutor.setKeepAliveSeconds(300);
           taskExecutor.setRejectedExecutionHandler(new ThreadPoolExecutor.CallerRunsPolicy());
           taskExecutor.setWaitForTasksToCompleteOnShutdown(true);
           taskExecutor.setAwaitTerminationSeconds(60);
           taskExecutor.initialize();
           return taskExecutor;
       }
    
    @Scheduled(cron = "0/1 * * * * ? ")
    public void punch() {
        ThreadPoolTaskExecutor asyncExecutor = getAsyncExecutor();
        asyncExecutor.execute(() -> {
            System.out.println("下班打卡提醒:" + DateUtils.dateTimeNow());
            //模拟打卡提醒业务流程处理
            try {
                Thread.sleep(1000 * 3);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        });
    }
    
    @Scheduled(cron = "0/1 * * * * ? ")
    public void eat() {
        System.out.println("食堂开饭提醒:" + DateUtils.dateTimeNow());
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31

    方案三

    加入配置类

    @Configuration
    public class ScheduleConfig implements SchedulingConfigurer {
        @Override
        public void configureTasks(ScheduledTaskRegistrar taskRegistrar) {
            ScheduledExecutorService scheduledExecutorService = new ScheduledThreadPoolExecutor(5);
            taskRegistrar.setScheduler(scheduledExecutorService);
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8

    不以物喜,不以己悲。

  • 相关阅读:
    Linux 处理文件( touch 命令、cp 命令、mv 命令、rm 命令)
    [云原生k8s] k8s资源限制以及探针检查
    JAVA【设计模式】桥接模式
    python读取图片
    HTTPS对HTTP的加密过程
    [Kettle] 生成记录
    SpringBoot-Dubbo中的Customer怎么获取了注册中心的服务呢?
    如何在报表控件FastReport.NET中连接XLSX 文件作为数据源?
    23.1 Bootstrap 表格
    【image captioning】CaMEL: Mean Teacher Learning for Image Captioning(实现流程)
  • 原文地址:https://blog.csdn.net/qq_35764295/article/details/126000705