Java定时任务的实现方式,一般是以下四种:
TimerScheduledExecutorServiceSpring TaskQuartz

Timer

Timer
这是java自带的java.util.Timer类,这个类允许你调度一个java.util.TimerTask任务。
使用这种方式可以让你的程序按照某一个频度执行,一般用的较少。
用Timer方式调用不支持多线程,且特定时间执行无效。
推荐使用ScheduledExecutorService替代Timer来调用。

public class TimeTask {
    public static void main(String[] args) {
        java.util.TimerTask timerTask = new java.util.TimerTask() {
            @Override
            public void run() {
                System.out.println("TimeTask: " + System.currentTimeMillis()/1000);
            }
        };
        // Timer方法不支持多线程,不推荐
//        Timer timer = new Timer();
//        timer.schedule(timerTask,0,3000);
        // 按时执行无效
//        timer.schedule(timerTask,new Date(2021, Calendar.MARCH,22,9,56));

        ScheduledExecutorService pool = new ScheduledThreadPoolExecutor(1);
        pool.scheduleAtFixedRate(timerTask,0,5, TimeUnit.SECONDS);
    }
}

ScheduledExecutorService

ScheduledExecutorService
支持延后和定期执行任务。
基于线程池操作,任务并发执行。
只有当调度任务来的时候才真正执行任务,其它时间处于轮询任务状态。

public class ScheduledExecutorServiceTask {
    public static void main(String[] args) {
        ScheduledExecutorService executor = Executors.newSingleThreadScheduledExecutor();
        executor.scheduleAtFixedRate(()->System.out.println("ScheduledExecutorServiceTask: " + System.currentTimeMillis()/1000), 0, 5, TimeUnit.SECONDS);
    }
}

Spring Task

Spring Task
Spring 3.0框架提供的计划任务,开发简单且执行效率高。
但在计划任务数量太多的时候,可能出现阻塞,崩溃,延迟启动等问题。
需要在此类中打上@Component@Scheduled注解以及在启动类中打上@EnableScheduling注解。

@Scheduled核心属性是cron,代表定时任务的触发计划表达式。这个表达式的格式为:
@Scheduled(cron="seconds minutes hours day month week year(可选)")
cron表达式中,每个位置的约束如下:

  • *:可用在所有字段中,表示对应时间域的每一个时刻,例如,*在分钟字段时,表示“每分钟”;
  • ?:该字符只在日期星期字段中使用,它通常指定为“无意义的值”,相当于占位符
  • -:表达一个范围,如在小时字段中使用“10-12”,则表示从10到12点,即10,11,12;
  • ,:表达一个列表值,如在星期字段中使用“MON,WED,FRI”,则表示星期一,星期三和星期五;
  • /:x/y表达一个等步长序列,x为起始值,y为增量步长值。如在秒数字段中使用0/15,则表示为0,15,30和45秒,而5/15在分钟字段中表示5,20,35,50,你也可以使用*/y,它等同于0/y;
  • #:该字符只能在星期字段中使用,表示当月某个工作日。如6#3表示当月的第三个星期五(6表示星期五,#3表示当前的第三个),而4#5表示当月的第五个星期三,假设当月没有第五个星期三,忽略不触发;
  • L:该字符只在日期星期字段中使用,代表“Last”的意思,但它在两个字段中意思不同。L在日期字段中,表示这个月份的最后一天,如一月的31号,非闰年二月的28号;如果L用在星期中,则表示星期六,等同于7。但是,如果L出现在星期字段里,而且在前面有一个数值X,则表示“这个月的最后X天”,例如,6L表示该月的最后星期五;
  • W:该字符只能出现在日期字段里,是对前导日期的修饰,表示离该日期最近的工作日。例如15W表示离该月15号最近的工作日,如果该月15号是星期六,则匹配14号星期五;如果15日是星期日,则匹配16号星期一;如果15号是星期二,那结果就是15号星期二。但必须注意关联的匹配日期不能够跨月,如你指定1W,如果1号是星期六,结果匹配的是3号星期一,而非上个月最后的那天。W字符串只能指定单一日期,而不能指定日期范围;
  • LW:在日期字段可以组合使用LW,它的意思是当月的最后一个工作日
  • C:该字符只在日期星期字段中使用,代表“Calendar”的意思。它的意思是计划所关联的日期,如果日期没有被关联,则相当于日历中所有日期。例如5C在日期字段中就相当于日历5日以后的第一天。1C在星期字段中相当于星期日后的第一天。

除了cron,还有两个常见的属性是fixedRatefixedDelay,他们和cron的区别如下代码:

@Component
public class SpringTask {

    private static final Logger logger = LoggerFactory.getLogger(SpringTask.class);
    private static int flag = 1;
    /**
     * @Scheduled(cron="seconds minutes hours day month week year(可选)")
     * cron:每5秒执行一次,但是睡眠了8秒。也就是说当第二次定时任务要启动的时候被阻塞了,只能推到下一个周期
     *       也就是[0 10 20 30]
     */
    @Scheduled(cron = "0/5 * * * * *")
    public void scheduled() throws InterruptedException {
        logger.error("=====>>>>>使用cron--" + flag++ + "  {}",System.currentTimeMillis()/1000);
        Thread.sleep(8000);
    }

    /**
     * fixedRate:每5秒为一个周期,如果执行时间超过5s则执行结束之后立马接着执行下一个,后面所有的任务都延迟了。
     *            也就是[0 6 12 18 24 30]
     */
    @Scheduled(fixedRate = 5000)
    public void scheduled1() throws InterruptedException {
        logger.error("=====>>>>>使用fixedRate--" + flag++ + "  {}", System.currentTimeMillis()/1000);
        Thread.sleep(6000);
    }

    /**
     * fixedDelay:不管任务执行多久,执行完后总是停5秒再执行第二次
     *             也就是第二次执行的时间为3s执行时长+5s等待=8s:[0 8 16 24 32 40]
     */
    @Scheduled(fixedDelay = 5000)
    public void scheduled2() throws InterruptedException {
        logger.error("=====>>>>>fixedDelay--" + flag++ + "  {}",System.currentTimeMillis()/1000);
        Thread.sleep(3000);
    }
}

Quartz

Quartz
基于SpringBoot 2.0,是完全有java编写的开源调度框架,需要引入依赖 org.quartz-scheduler,并需要在启动类打上@EnableScheduling注解。
只需要定义 Job(任务)、Trigger(触发器)和Scheduler(调度器) 就可以实现定时调度功能。
它适合运用于比Spring Task更为复杂的定时任务系统中,并广泛运用于分布式上。

依赖:

<dependency>
    <groupId>org.quartz-scheduler</groupId>
    <artifactId>quartz</artifactId>
    <version>2.3.2</version>
</dependency>

执行内容:

public class QuartzTask extends QuartzJobBean {

    private static final Logger logger = LoggerFactory.getLogger(QuartzTask.class);
    private static int flag = 1;

    @Override
    protected void executeInternal(JobExecutionContext jobExecutionContext) throws JobExecutionException {
        logger.error("QuartzTask--" + flag++ + ":{}",System.currentTimeMillis()/1000);
    }
}

环境初始化:

@Configuration
public class QuartzConfig {

    /**
     * 创建Job对象(应该会默认交给Spring容器进行管理)
     */
    @Bean
    public JobDetail teatQuartzDetail(){
        return JobBuilder.newJob(QuartzTask.class).withIdentity("QuartzTask").storeDurably().build();
    }

    /**
     * 管理Trigger对象,相当于管理一个cron表达式
     */
    @Bean
    public Trigger testQuartzTrigger(){
        SimpleScheduleBuilder scheduleBuilder = SimpleScheduleBuilder.simpleSchedule()
                .withIntervalInSeconds(5)
                .repeatForever();

        return TriggerBuilder.newTrigger().forJob(teatQuartzDetail())
                .withIdentity("QuartzTask")
                .withSchedule(scheduleBuilder)
                .build();
    }
}

还有一种方案是基于分布式的配置,传送门:SpringBoot定时任务(schedule、quartz)
现在主流的框架/脚手架用的也都是这一套,有空可以根据若依或者是人人开源的脚手架学习一下。

Last modification:June 7th, 2021 at 07:59 pm
喵ฅฅ