0%

Spring定时任务

文章字数:675,阅读全文大约需要2分钟

通过类上设置注解@EnableScheduling可以开启spring自带的定时任务,@Scheduled设置定时时间。还可以通过ThreadPoolTaskSchedulerschedule(Runable,cron)动态添加

使用

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
@Component
@EnableScheduling //定时任务
public class SchedulingTest{
//每30秒执行一次
@Scheduled(fixedRate = 1000 * 30)
public void doSomeThing(){
//没30秒输出一次
System.out.println ("定时输出:" + dateFormat ().format (new Date ()));
}

//在固定时间执行(当时间匹配规则时输出)
@Scheduled(cron = "0 */1 * * * * ")
public void reportCurrentByCron(){
System.out.println ("固定时间输出" + dateFormat ().format (new Date()));
}

private SimpleDateFormat dateFormat(){
return new SimpleDateFormat ("HH:mm:ss");
}
}

固定时间匹配规则

字段 允许值 允许的特殊字符
0-59 , - * /
0-59 , - * /
小时 0-23 , - * /
日期 1-31 , - * ? / L W C
月份 1-12 或者 JAN-DEC , - * /
星期 1-7 或者 SUN-SAT , - * ? / L C #
空, 1970-2099 , - * /
  • 每个元素都可以是一个值如6,一个区间9-12 一个间隔时间8-18/4 /4代表间隔四个小时,一个列表1,3,5
  • 日期和星期互斥,即两个元素重合,必须其中一个设置?忽略
  • *代表所有可能的值
  • /指定数值的增量,如0/10(分钟单位中)代表0分钟开始,10分钟执行一次
  • ?仅在日期和星期中,代表不指定值
  • L用于日期和星期中,代表倒数第几个
  • W仅在日其中,代表平日(工作日)。15W代表离15号最近的一个工作日。
  • C日期,5C五个单位后的第一天
  • #每个月第几个星期几,例如在4#2表示某月的第二个星期三。

转换异步线程

单线程执行时间超过定时间隔可能会出现任务丢失的情况,可以使用异步线程避免这个问题。

  • 配置Spring@EnableAsync
  • 执行方法上配置任务线程池@Async
    1
    2
    3
    4
    5
    6
    //每30秒执行一次
    @Async("taskExecutor")
    @Scheduled(fixedRate = 1000 * 3)
    public void xxx(){
    //...
    }

分布式情况下避免重复执行

  1. lock = redisTemplate.opsForValue().setIfAbsent(KEY, LOCK);采用Redis判断是否存在key,不存在则设置key,执行完成删除key的方式加锁(跨时区部署还是会重复执行)
  2. shedlock加锁
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    <dependency>
    <groupId>net.javacrumbs.shedlock</groupId>
    <artifactId>shedlock-spring</artifactId>
    <version>0.16.1</version>
    </dependency>

    <dependency>
    <groupId>net.javacrumbs.shedlock</groupId>
    <artifactId>shedlock-provider-jdbc-template</artifactId>
    <version>0.16.1</version>
    </dependency>
    配置(jdbc),还有redis,mongo,zookeeper等锁的实现
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    @Configuration
    @EnableScheduling
    public class ShedlockConfig {

    @Bean
    public LockProvider lockProvider(DataSource dataSource) {
    return new JdbcTemplateLockProvider(dataSource);
    }

    @Bean
    public ScheduledLockConfiguration scheduledLockConfiguration(LockProvider lockProvider) {
    return ScheduledLockConfigurationBuilder
    .withLockProvider(lockProvider)
    .withPoolSize(10)
    .withDefaultLockAtMostFor(Duration.ofMinutes(10))//lock expire最大时间10分钟
    .build();
    }
    }
    shedlock表
    1
    2
    3
    4
    5
    6
    7
    CREATE TABLE shedlock(
    name VARCHAR(64),
    lock_until TIMESTAMP(3) NULL,
    locked_at TIMESTAMP(3) NULL,
    locked_by VARCHAR(255),
    PRIMARY KEY (name)
    )
    加锁
    1
    2
    3
    4
    5
    @Scheduled(fixedDelay = 10*1000 /**ms**/,initialDelay = 10*1000)
    @SchedulerLock(name = "demoLockTask", lockAtMostFor = 5*1000)
    public void checkTask(){
    LOGGER.error("checkTask executed");
    }

动态添加关闭定时任务

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
// 引入定时调度线程池
@Autowired
private ThreadPoolTaskScheduler threadPoolTaskScheduler;

// 接收线程调度返回的结果获取类
private ScheduledFuture<?> future;

public void test(){
// 如果已经有任务了就取消原有任务
if (future != null) {
future.cancel(true);
}
// 每月第一天
String cron = "0 0 0 1 * ?";
future = threadPoolTaskScheduler.schedule(new MyRunnable(), new CronTrigger(cron));
// Runnable也可以写成lambda
future = threadPoolTaskScheduler.schedule(()->{
//do..
}, new CronTrigger(cron));
}