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

发布于:2022-07-26 ⋅ 阅读:(577) ⋅ 点赞:(0)

问题

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

原因

上代码

 @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());
 }

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

下班打卡提醒:20220726182219
食堂开饭提醒:20220726182222
下班打卡提醒:20220726182223
食堂开饭提醒:20220726182226
下班打卡提醒:20220726182227
食堂开饭提醒:20220726182230
下班打卡提醒:20220726182231

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

食堂开饭提醒:20220726182511
食堂开饭提醒:20220726182512
食堂开饭提醒:20220726182513
食堂开饭提醒:20220726182514
食堂开饭提醒:20220726182515
食堂开饭提醒:20220726182516
食堂开饭提醒:20220726182517

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

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

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

@Override
public void run() {
	try {
		this.delegate.run();
	}
	catch (UndeclaredThrowableException ex) {
		this.errorHandler.handleError(ex.getUndeclaredThrowable());
	}
	catch (Throwable ex) {
		this.errorHandler.handleError(ex);
	}
}

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

解决

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

方案一

先看一波官网咋搞

@Async
void doSomething() {
    // this will be run asynchronously
}

加异步注解,确实可行,亲测可用
官网参考:
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());
}

方案三

加入配置类

@Configuration
public class ScheduleConfig implements SchedulingConfigurer {
    @Override
    public void configureTasks(ScheduledTaskRegistrar taskRegistrar) {
        ScheduledExecutorService scheduledExecutorService = new ScheduledThreadPoolExecutor(5);
        taskRegistrar.setScheduler(scheduledExecutorService);
    }
}

不以物喜,不以己悲。

本文含有隐藏内容,请 开通VIP 后查看