后台开发笔记

@Scheduled多线程执行定时任务

2021-04-09  本文已影响0人  Figo_OU

@Scheduled 这个注解给我们带了很大的方便,我们只要加上该注解,并且根据需求设置好就可以使用定时任务了。

但是,我们需要注意的是,@Scheduled 并不一定一定会按时执行。

关于cron表达式详解可以看这里:https://www.jianshu.com/p/1defb0f22ed1
是不是懵了?我们看看代码

//定时任务1,每秒运行一次
@Scheduled(cron = "0/1 * * * * ?")
    public void autoRun() {
        Date date = new Date();
        Date oneMinLater = new Date(date.getTime() + 60 * 1000);
//        runJob(()->{//分布式锁
            log.info("begin-------自动执行形象进度日记录");
            Date date1 = new Date();
            //模拟在做一个耗时很长的事情。
            while (oneMinLater.getTime() > date1.getTime()) {
                date1 = new Date();
            }
            log.info("end-------自动执行形象进度日记录完成");
//        },"形象进度");
    }

//定时任务2,每5秒运行一次
@Scheduled(cron = "0/5 * * * * ? ")
    public void checkBimModelTreeBuildTimeout() {
        String desc = "检查构建树超时情况";
//        runJob(()->{
        log.info("begin-------{}" ,desc);
        log.info("end-------{}" ,desc);
//        }, desc);
    }
结果:
15:38:17.001 [ozxThreadPool-1-thread-1] INFO  c.b.b.p.job.GraphicProgressJob - begin-------自动执行形象进度日记录

而我们预期的结果:
15:40:07.001 [ozxThreadPool-1-thread-1] INFO  c.b.b.p.job.GraphicProgressJob - begin-------自动执行形象进度日记录
15:40:10.001 [ozxThreadPool-1-thread-2] INFO  c.b.b.p.j.BimModelTreeBuildTimeoutJob - begin-------检查构建树超时情况
15:40:10.001 [ozxThreadPool-1-thread-2] INFO  c.b.b.p.j.BimModelTreeBuildTimeoutJob - end-------检查构建树超时情况
15:40:15.002 [ozxThreadPool-1-thread-4] INFO  c.b.b.p.j.BimModelTreeBuildTimeoutJob - begin-------检查构建树超时情况
15:40:15.002 [ozxThreadPool-1-thread-4] INFO  c.b.b.p.j.BimModelTreeBuildTimeoutJob - end-------检查构建树超时情况

所以定时任务2并没有执行
因为使用@Scheduled 的定时任务虽然是异步执行的,但是,不同的定时任务之间并不是并行的!!!!!!!!

在其中一个定时任务没有执行完之前,其他的定时任务即使是到了执行时间,也是不会执行的,它们会进行排队。

也就是如果你想你不同的定时任务互不影响,到时间就会执行,那么你最好将你的定时任务方法自己搞成异步方法,这样,

定时任务其实就相当于调用了一个线程执行任务,一瞬间就结束了。当然,也可以勉强当做是任务都会定时执行。

加上下面这个配置类就可以使定时任务变成异步的了

@Configuration
public class ScheduleConfig implements SchedulingConfigurer {

    @Value("${aib.threadPool.configList[1].name:schedule}")
    private String schedule;

    public static final String DEFAULT_SCHEDULE_POOL_NAME = "schedule";
    public static final Integer COREPOOLSIZE   = 8;
    public static final Integer MAXIMUMPOOLSIZE   = COREPOOLSIZE * 2;
    public static final Integer WAITQUEUECAPACITY   = 100;

    @Override
    public void configureTasks(ScheduledTaskRegistrar scheduledTaskRegistrar) {
        ThreadTaskPool taskPool = ThreadTaskPools.getInstance().getTaskPool(schedule);
        if (null == taskPool) {
//            return;
            ThreadPoolConfig threadPoolConfig = new ThreadPoolConfig("ozxThreadPool", ThreadTaskPoolType.SCHEDULE, COREPOOLSIZE,MAXIMUMPOOLSIZE,WAITQUEUECAPACITY);
            taskPool = new ThreadTaskPool(threadPoolConfig);
        }
        ThreadPoolExecutor threadPoolExecutor = taskPool.getThreadPoolExecutor();
        //创建scheduledExecutorService线程池
//        ScheduledExecutorService scheduledExecutorService = Executors.newScheduledThreadPool(8, new PoolThreadFactory("ozxThreadPool"));
        //解决ScheduledExecutorService最大线程数Integer.MAX_VALUE问题
//                  ((ScheduledThreadPoolExecutor) scheduledExecutorService).setMaximumPoolSize(16);

        scheduledTaskRegistrar.setScheduler(threadPoolExecutor);
    }
}

ps:【大家都说不要用Executors的各种构造方法去实例化线程池。其实主要的问题是newScheduledThreadPool的构造方法中是这么写的】

public ScheduledThreadPoolExecutor(int corePoolSize,
                                       ThreadFactory threadFactory) {
        super(corePoolSize, Integer.MAX_VALUE, 0, NANOSECONDS,
              new DelayedWorkQueue(), threadFactory);//因为Integer.MAX_VALUE,所以可能会造成OOM
    }

我的理解是只要修改最大线程数就好了。((ScheduledThreadPoolExecutor) scheduledExecutorService).setMaximumPoolSize(16);

说明:Executors各个方法的弊端:
1)newFixedThreadPool和newSingleThreadExecutor:
  主要问题是堆积的请求处理队列可能会耗费非常大的内存,甚至OOM。
2)newCachedThreadPool和newScheduledThreadPool:
  主要问题是线程数最大数是Integer.MAX_VALUE,可能会创建数量非常多的线程,甚至OOM。

上一篇下一篇

猜你喜欢

热点阅读