定时器(注解方式)

原文:https://www.cnblogs.com/domi22/p/9418433.html

1、spring boot使用注解实现定时器

@EnableScheduling
@Component
public class TestTimer {
    @Scheduled(cron="*/1 * * * * ?")
    public void testOne() {
        System.out.println("第一个"+"每1秒执行一次"+Thread.currentThread().getName());
    }
 
    @Scheduled(fixedRate=1000)
    public void testTwo() {
        System.out.println("第二个"+"每1秒执行一次"+Thread.currentThread().getName());
    }
}

这样项目启动的时候定时器就启动了,但是通过打印线程名可以发现,这两个任务是同一个线程。

如果将第二个任务睡眠10秒,模拟延时。

@EnableScheduling
@Component
public class TestTimer {
    @Scheduled(cron="*/1 * * * * ?")
    public void testOne() {
        System.out.println("第一个"+"每1秒执行一次"+Thread.currentThread().getName());
    }
 
    @Scheduled(fixedRate=1000)
    public void testTwo() {
        System.out.println("第二个"+"每1秒执行一次"+Thread.currentThread().getName());
        try {
            Thread.sleep(10000);
        } catch (InterruptedException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        }
    }
}

会发现不但自己的任务丢失了,也造成了其他任务的丢失。
问题1:单线程任务丢失
解决办法:采用异步的方式执行调度任务,配置 Spring 的 @EnableAsync,在执行定时任务的方法上标注 @Async配置任务执行池

@EnableScheduling
@EnableAsync
@Component
public class TestTimer {
    
    @Async
    @Scheduled(cron="*/1 * * * * ?")
    public void testOne() {
        System.out.println("第一个"+"每1秒执行一次"+Thread.currentThread().getName());
    }
    @Async
    @Scheduled(fixedRate=1000)
    public void testTwo() {
        System.out.println("第二个"+"每1秒执行一次"+Thread.currentThread().getName());
        try {
            Thread.sleep(10000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
}

注意:@Async所使用的线程池容量为100
3.配置线程池大小
虽然上面的方式已经解决了我们的问题,但是总觉得不太好,有时候我们需要异步执行任务,但是又不需要这么多的线程的时候,我们可以使用下面的配置来设置线程池的大小
配置文件:

@Configuration
public class AsyncConfig {

    @Bean("taskExecutor")
    public Executor taskExecutor1() {
        ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();
        executor.setCorePoolSize(10);
        executor.setMaxPoolSize(20);
        executor.setQueueCapacity(200);
        executor.setKeepAliveSeconds(60);
        executor.setThreadNamePrefix("taskExecutor-");
        executor.setRejectedExecutionHandler(new ThreadPoolExecutor.CallerRunsPolicy());
        executor.setWaitForTasksToCompleteOnShutdown(true);
        executor.setAwaitTerminationSeconds(60);
        return executor;
    }

}

问题2:在分布式系统下,重复执行的问题。

解决方式1:可以使用redis的分布式锁保证spring schedule集群只执行一次。 redis分布式锁是通过setnx命令实现的。该命令的作用是,当往redis中存入一个值时,会先判断该值对应的key是否存在,如果存在则返回0,如果不存在,则将该值存入redis并返回1。(但是在分布式跨时区部署的时候,依然无法避免重复执行)

解决方式2:可以通过使用shedlock将spring schedule上锁。详细见:https://segmentfault.com/a/1190000011975027

问题3:如果使用异步多线程方式,会有并发安全问题。
如果想有的定时任务不考虑并发安全使用@Async注解,有的定时任务需要考虑线程并发使用同步阻塞方式。

@EnableScheduling
@EnableAsync 
@Component
public class TestTimer {
    
    @Scheduled(cron="*/1 * * * * ?")
    public void testOne() throws InterruptedException {
        System.out.println("任务1====>"+Thread.currentThread().getName());
        Thread.sleep(10000); //模拟延时
    }
    
    @Async
    @Scheduled(cron="*/1 * * * * ?")
    public void testTwo() throws InterruptedException {
        System.out.println("任务2====>"+Thread.currentThread().getName());
    }
}

发现任务2还是会被任务1阻塞住,好像没什么好办法?

待解决:问题3

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