定时任务_spring 定时任务 servlet定时任务 - CSDN
定时任务 订阅
定时任务是一款系统安全类软件,支持Android 2.1。 展开全文
定时任务是一款系统安全类软件,支持Android 2.1。
信息
应用名称
定时任务
应用版本
0.3
运行环境
Android 2.1
应用平台
mobile
应用类型
系统安全类
三维重建简介
Timed Tasks是一款Wi-Fi、飞行模式、蓝牙、数据连接、屏幕亮度定时开关任务软件,让各项设置按自己所设的时间、日期开启或关闭。
收起全文
  • 前面一篇文章(SpringBoot-定时任务)中介绍了如何用SpringBoot框架中的注解方式来实现定时任务,这种方式的好处是不使用第三方的依赖,仅凭几个方便的注解,即可编写一个简单的定时任务处理。 实际开发中为了满足复杂...

    写在前面

    前面一篇文章(SpringBoot-定时任务)中介绍了如何用SpringBoot框架中的注解方式来实现定时任务,这种方式的好处是不使用第三方的依赖,仅凭几个方便的注解,即可编写一个简单的定时任务处理。

    实际开发中为了满足复杂的业务场景,比如多个节点之间的任务切换、恢复等,对任务本身的暂停、启动、执行时间修改等操作,使用简单的定时任务就很难满足了。这一节我们来学习SpringBoot集成Quartz框架来实现复杂的任务调度处理。

    Quzrtz简介

    Quartz是一个由Java语言编写的开源任务调度框架,具有简单高效、容错、支持分布式等优点。

    主要API:

    • Scheduler:与调度器交互的主要API。
    • Job:需要被调度器调度的任务必须实现的接口。
    • JobDetail:用于定义任务的实例。
    • Trigger:用于定义调度器何时调度任务执行的组件。
    • JobBuilder:用于定义或创建JobDetail的实例 。
    • TriggerBuilder:用于定义或创建触发器实例。

    数据库准备

    Quartz提供两种基本作业存储类型。第一种类型叫做RAMJobStore,第二种类型叫做JDBC作业存储。
    RAMJobStore的好处是不需要外部数据库的支持,作业存储在内存中,执行效率优于数据库保存的方式。缺点是一旦应用奔溃或者服务器宕机,任务作业数据就会丢失,所以这种方式是不能支持分布式部属的。

    这里我们使用JDBC类型的存储方式,需要执行以下SQL语句:

    DROP TABLE IF EXISTS QRTZ_FIRED_TRIGGERS;
    DROP TABLE IF EXISTS QRTZ_PAUSED_TRIGGER_GRPS;
    DROP TABLE IF EXISTS QRTZ_SCHEDULER_STATE;
    DROP TABLE IF EXISTS QRTZ_LOCKS;
    DROP TABLE IF EXISTS QRTZ_SIMPLE_TRIGGERS;
    DROP TABLE IF EXISTS QRTZ_SIMPROP_TRIGGERS;
    DROP TABLE IF EXISTS QRTZ_CRON_TRIGGERS;
    DROP TABLE IF EXISTS QRTZ_BLOB_TRIGGERS;
    DROP TABLE IF EXISTS QRTZ_TRIGGERS;
    DROP TABLE IF EXISTS QRTZ_JOB_DETAILS;
    DROP TABLE IF EXISTS QRTZ_CALENDARS;
    
    -- 存储每一个已配置的Job的详细信息
    CREATE TABLE QRTZ_JOB_DETAILS(
    SCHED_NAME VARCHAR(120) NOT NULL,
    JOB_NAME VARCHAR(200) NOT NULL,
    JOB_GROUP VARCHAR(200) NOT NULL,
    DESCRIPTION VARCHAR(250) NULL,
    JOB_CLASS_NAME VARCHAR(250) NOT NULL,
    IS_DURABLE VARCHAR(1) NOT NULL,
    IS_NONCONCURRENT VARCHAR(1) NOT NULL,
    IS_UPDATE_DATA VARCHAR(1) NOT NULL,
    REQUESTS_RECOVERY VARCHAR(1) NOT NULL,
    JOB_DATA BLOB NULL,
    PRIMARY KEY (SCHED_NAME,JOB_NAME,JOB_GROUP))
    ENGINE=InnoDB;
    
    -- 存储已配置的Trigger的信息
    CREATE TABLE QRTZ_TRIGGERS (
    SCHED_NAME VARCHAR(120) NOT NULL,
    TRIGGER_NAME VARCHAR(200) NOT NULL,
    TRIGGER_GROUP VARCHAR(200) NOT NULL,
    JOB_NAME VARCHAR(200) NOT NULL,
    JOB_GROUP VARCHAR(200) NOT NULL,
    DESCRIPTION VARCHAR(250) NULL,
    NEXT_FIRE_TIME BIGINT(13) NULL,
    PREV_FIRE_TIME BIGINT(13) NULL,
    PRIORITY INTEGER NULL,
    TRIGGER_STATE VARCHAR(16) NOT NULL,
    TRIGGER_TYPE VARCHAR(8) NOT NULL,
    START_TIME BIGINT(13) NOT NULL,
    END_TIME BIGINT(13) NULL,
    CALENDAR_NAME VARCHAR(200) NULL,
    MISFIRE_INSTR SMALLINT(2) NULL,
    JOB_DATA BLOB NULL,
    PRIMARY KEY (SCHED_NAME,TRIGGER_NAME,TRIGGER_GROUP),
    FOREIGN KEY (SCHED_NAME,JOB_NAME,JOB_GROUP)
    REFERENCES QRTZ_JOB_DETAILS(SCHED_NAME,JOB_NAME,JOB_GROUP))
    ENGINE=InnoDB;
    
    -- 存储已配置的Simple Trigger的信息
    CREATE TABLE QRTZ_SIMPLE_TRIGGERS (
    SCHED_NAME VARCHAR(120) NOT NULL,
    TRIGGER_NAME VARCHAR(200) NOT NULL,
    TRIGGER_GROUP VARCHAR(200) NOT NULL,
    REPEAT_COUNT BIGINT(7) NOT NULL,
    REPEAT_INTERVAL BIGINT(12) NOT NULL,
    TIMES_TRIGGERED BIGINT(10) NOT NULL,
    PRIMARY KEY (SCHED_NAME,TRIGGER_NAME,TRIGGER_GROUP),
    FOREIGN KEY (SCHED_NAME,TRIGGER_NAME,TRIGGER_GROUP)
    REFERENCES QRTZ_TRIGGERS(SCHED_NAME,TRIGGER_NAME,TRIGGER_GROUP))
    ENGINE=InnoDB;
    
    -- 存储Cron Trigger,包括Cron表达式和时区信息
    CREATE TABLE QRTZ_CRON_TRIGGERS (
    SCHED_NAME VARCHAR(120) NOT NULL,
    TRIGGER_NAME VARCHAR(200) NOT NULL,
    TRIGGER_GROUP VARCHAR(200) NOT NULL,
    CRON_EXPRESSION VARCHAR(120) NOT NULL,
    TIME_ZONE_ID VARCHAR(80),
    PRIMARY KEY (SCHED_NAME,TRIGGER_NAME,TRIGGER_GROUP),
    FOREIGN KEY (SCHED_NAME,TRIGGER_NAME,TRIGGER_GROUP)
    REFERENCES QRTZ_TRIGGERS(SCHED_NAME,TRIGGER_NAME,TRIGGER_GROUP))
    ENGINE=InnoDB;
    
    CREATE TABLE QRTZ_SIMPROP_TRIGGERS
      (
        SCHED_NAME VARCHAR(120) NOT NULL,
        TRIGGER_NAME VARCHAR(200) NOT NULL,
        TRIGGER_GROUP VARCHAR(200) NOT NULL,
        STR_PROP_1 VARCHAR(512) NULL,
        STR_PROP_2 VARCHAR(512) NULL,
        STR_PROP_3 VARCHAR(512) NULL,
        INT_PROP_1 INT NULL,
        INT_PROP_2 INT NULL,
        LONG_PROP_1 BIGINT NULL,
        LONG_PROP_2 BIGINT NULL,
        DEC_PROP_1 NUMERIC(13,4) NULL,
        DEC_PROP_2 NUMERIC(13,4) NULL,
        BOOL_PROP_1 VARCHAR(1) NULL,
        BOOL_PROP_2 VARCHAR(1) NULL,
        PRIMARY KEY (SCHED_NAME,TRIGGER_NAME,TRIGGER_GROUP),
        FOREIGN KEY (SCHED_NAME,TRIGGER_NAME,TRIGGER_GROUP)
        REFERENCES QRTZ_TRIGGERS(SCHED_NAME,TRIGGER_NAME,TRIGGER_GROUP))
    ENGINE=InnoDB;
    
    -- Trigger作为Blob类型存储(用于Quartz用户用JDBC创建他们自己定制的Trigger类型,JobStore并不知道如何存储实例的时候)
    CREATE TABLE QRTZ_BLOB_TRIGGERS (
    SCHED_NAME VARCHAR(120) NOT NULL,
    TRIGGER_NAME VARCHAR(200) NOT NULL,
    TRIGGER_GROUP VARCHAR(200) NOT NULL,
    BLOB_DATA BLOB NULL,
    PRIMARY KEY (SCHED_NAME,TRIGGER_NAME,TRIGGER_GROUP),
    INDEX (SCHED_NAME,TRIGGER_NAME, TRIGGER_GROUP),
    FOREIGN KEY (SCHED_NAME,TRIGGER_NAME,TRIGGER_GROUP)
    REFERENCES QRTZ_TRIGGERS(SCHED_NAME,TRIGGER_NAME,TRIGGER_GROUP))
    ENGINE=InnoDB;
    
    -- 以Blob类型存储Quartz的Calendar日历信息,quartz可配置一个日历来指定一个时间范围
    CREATE TABLE QRTZ_CALENDARS (
    SCHED_NAME VARCHAR(120) NOT NULL,
    CALENDAR_NAME VARCHAR(200) NOT NULL,
    CALENDAR BLOB NOT NULL,
    PRIMARY KEY (SCHED_NAME,CALENDAR_NAME))
    ENGINE=InnoDB;
    
    -- 存储已暂停的Trigger组的信息
    CREATE TABLE QRTZ_PAUSED_TRIGGER_GRPS (
    SCHED_NAME VARCHAR(120) NOT NULL,
    TRIGGER_GROUP VARCHAR(200) NOT NULL,
    PRIMARY KEY (SCHED_NAME,TRIGGER_GROUP))
    ENGINE=InnoDB;
    
    -- 存储与已触发的Trigger相关的状态信息,以及相联Job的执行信息
    CREATE TABLE QRTZ_FIRED_TRIGGERS (
    SCHED_NAME VARCHAR(120) NOT NULL,
    ENTRY_ID VARCHAR(95) NOT NULL,
    TRIGGER_NAME VARCHAR(200) NOT NULL,
    TRIGGER_GROUP VARCHAR(200) NOT NULL,
    INSTANCE_NAME VARCHAR(200) NOT NULL,
    FIRED_TIME BIGINT(13) NOT NULL,
    SCHED_TIME BIGINT(13) NOT NULL,
    PRIORITY INTEGER NOT NULL,
    STATE VARCHAR(16) NOT NULL,
    JOB_NAME VARCHAR(200) NULL,
    JOB_GROUP VARCHAR(200) NULL,
    IS_NONCONCURRENT VARCHAR(1) NULL,
    REQUESTS_RECOVERY VARCHAR(1) NULL,
    PRIMARY KEY (SCHED_NAME,ENTRY_ID))
    ENGINE=InnoDB;
    
    -- 存储少量的有关 Scheduler的状态信息,和别的 Scheduler 实例(假如是用于一个集群中)
    CREATE TABLE QRTZ_SCHEDULER_STATE (
    SCHED_NAME VARCHAR(120) NOT NULL,
    INSTANCE_NAME VARCHAR(200) NOT NULL,
    LAST_CHECKIN_TIME BIGINT(13) NOT NULL,
    CHECKIN_INTERVAL BIGINT(13) NOT NULL,
    PRIMARY KEY (SCHED_NAME,INSTANCE_NAME))
    ENGINE=InnoDB;
    -- 存储程序的非观锁的信息(假如使用了悲观锁)
    CREATE TABLE QRTZ_LOCKS (
    SCHED_NAME VARCHAR(120) NOT NULL,
    LOCK_NAME VARCHAR(40) NOT NULL,
    PRIMARY KEY (SCHED_NAME,LOCK_NAME))
    ENGINE=InnoDB;
    
    CREATE INDEX IDX_QRTZ_J_REQ_RECOVERY ON QRTZ_JOB_DETAILS(SCHED_NAME,REQUESTS_RECOVERY);
    CREATE INDEX IDX_QRTZ_J_GRP ON QRTZ_JOB_DETAILS(SCHED_NAME,JOB_GROUP);
    
    CREATE INDEX IDX_QRTZ_T_J ON QRTZ_TRIGGERS(SCHED_NAME,JOB_NAME,JOB_GROUP);
    CREATE INDEX IDX_QRTZ_T_JG ON QRTZ_TRIGGERS(SCHED_NAME,JOB_GROUP);
    CREATE INDEX IDX_QRTZ_T_C ON QRTZ_TRIGGERS(SCHED_NAME,CALENDAR_NAME);
    CREATE INDEX IDX_QRTZ_T_G ON QRTZ_TRIGGERS(SCHED_NAME,TRIGGER_GROUP);
    CREATE INDEX IDX_QRTZ_T_STATE ON QRTZ_TRIGGERS(SCHED_NAME,TRIGGER_STATE);
    CREATE INDEX IDX_QRTZ_T_N_STATE ON QRTZ_TRIGGERS(SCHED_NAME,TRIGGER_NAME,TRIGGER_GROUP,TRIGGER_STATE);
    CREATE INDEX IDX_QRTZ_T_N_G_STATE ON QRTZ_TRIGGERS(SCHED_NAME,TRIGGER_GROUP,TRIGGER_STATE);
    CREATE INDEX IDX_QRTZ_T_NEXT_FIRE_TIME ON QRTZ_TRIGGERS(SCHED_NAME,NEXT_FIRE_TIME);
    CREATE INDEX IDX_QRTZ_T_NFT_ST ON QRTZ_TRIGGERS(SCHED_NAME,TRIGGER_STATE,NEXT_FIRE_TIME);
    CREATE INDEX IDX_QRTZ_T_NFT_MISFIRE ON QRTZ_TRIGGERS(SCHED_NAME,MISFIRE_INSTR,NEXT_FIRE_TIME);
    CREATE INDEX IDX_QRTZ_T_NFT_ST_MISFIRE ON QRTZ_TRIGGERS(SCHED_NAME,MISFIRE_INSTR,NEXT_FIRE_TIME,TRIGGER_STATE);
    CREATE INDEX IDX_QRTZ_T_NFT_ST_MISFIRE_GRP ON QRTZ_TRIGGERS(SCHED_NAME,MISFIRE_INSTR,NEXT_FIRE_TIME,TRIGGER_GROUP,TRIGGER_STATE);
    
    CREATE INDEX IDX_QRTZ_FT_TRIG_INST_NAME ON QRTZ_FIRED_TRIGGERS(SCHED_NAME,INSTANCE_NAME);
    CREATE INDEX IDX_QRTZ_FT_INST_JOB_REQ_RCVRY ON QRTZ_FIRED_TRIGGERS(SCHED_NAME,INSTANCE_NAME,REQUESTS_RECOVERY);
    CREATE INDEX IDX_QRTZ_FT_J_G ON QRTZ_FIRED_TRIGGERS(SCHED_NAME,JOB_NAME,JOB_GROUP);
    CREATE INDEX IDX_QRTZ_FT_JG ON QRTZ_FIRED_TRIGGERS(SCHED_NAME,JOB_GROUP);
    CREATE INDEX IDX_QRTZ_FT_T_G ON QRTZ_FIRED_TRIGGERS(SCHED_NAME,TRIGGER_NAME,TRIGGER_GROUP);
    CREATE INDEX IDX_QRTZ_FT_TG ON QRTZ_FIRED_TRIGGERS(SCHED_NAME,TRIGGER_GROUP);
    
    commit;
    

    创建项目

    快速构建一个SpringBoot项目,添加依赖:

    <dependency>
    	<groupId>org.springframework.boot</groupId>
    	<artifactId>spring-boot-starter-data-jpa</artifactId>
    </dependency>
    <dependency>
    	<groupId>org.springframework.boot</groupId>
    	<artifactId>spring-boot-starter-web</artifactId>
    </dependency>
    <dependency>
    	<groupId>mysql</groupId>
    	<artifactId>mysql-connector-java</artifactId>
    </dependency>
    <dependency>
    	<groupId>org.springframework.boot</groupId>
    	<artifactId>spring-boot-starter-quartz</artifactId>
    </dependency>
    <dependency>
    	<groupId>org.projectlombok</groupId>
    	<artifactId>lombok</artifactId>
    	<optional>true</optional>
    </dependency>
    

    创建项目的包结构:
    在这里插入图片描述
    在application.yml中添加如下配置文件:

    server:
      port: 8080
    spring:
      application:
        name: boot-quartz-demo
      datasource: #数据库配置
        driver-class-name: com.mysql.jdbc.Driver
        url: jdbc:mysql://127.0.0.1:3306/quartz?serverTimezone=UTC&useUnicode=true&characterEncoding=utf-8&useSSL=true
        username: root
        password: 123456
      jpa: #JPA配置
        database: mysql
        show-sql: true
      quartz:
        #quartz相关属性配置
        properties:
          org:
            quartz:
              scheduler:
                instanceName: clusteredScheduler #调度器的实例名
                instanceId: AUTO #调度器编号自动生成
              jobStore:
                class: org.quartz.impl.jdbcjobstore.JobStoreTX
                driverDelegateClass: org.quartz.impl.jdbcjobstore.StdJDBCDelegate
                tablePrefix: qrtz_ #数据库表名前缀
                isClustered: true #开启分布式部署
                clusterCheckinInterval: 10000 #分布式节点有效性检查时间间隔,单位:秒
                useProperties: false
              threadPool:
                class: org.quartz.simpl.SimpleThreadPool #自带的线程池实现类
                threadCount: 10 #开启10个线程
                threadPriority: 5 #工作者线程的优先级
                threadsInheritContextClassLoaderOfInitializingThread: true
        #数据库方式
        job-store-type: jdbc
    

    构建好如上项目结构后,我们开始编写实现代码,首先我们需要封装一个任务执行的类存放与model包下:

    /**
     * @ClassName Task
     * @Description 定时任务基本属性
     */
    @Data
    @Builder
    public class Task {
    
        /**
         * 任务名称
         */
        @NotEmpty(message = "任务名称不能为空")
        private String name;
    
        /**
         * 任务分组
         */
        @NotEmpty(message = "任务分组不能为空")
        private String goup;
    
        /**
         * corn表达式
         */
        @NotEmpty(message = "定时任务的表达式不能为空")
        private String cron;
    
        /**
         * 任务描述
         */
        private String desc;
    
        /**
         * 执行任务的逻辑类
         */
        @NotNull(message = "执行任务的逻辑类名不能为空")
        private String jobClass;
    
        /**
         * 元数据
         */
        private Map<?,?> jobDataMap;
    
        public JobKey getJobKey(){
            return JobKey.jobKey(this.name,this.goup);
        }
    }
    

    编写业务类QuartzJobService存放与service包下:

    @Slf4j
    @Service
    public class QuartzJobService {
    
        //Quartz定时任务核心的功能实现类
        private Scheduler scheduler;
    
        public QuartzJobService(@Autowired SchedulerFactoryBean schedulerFactoryBean) {
            scheduler = schedulerFactoryBean.getScheduler();
        }
    
        /**
         * 创建和启动定时任务
         * {@link org.quartz.Scheduler#scheduleJob(JobDetail, Trigger)}
         *
         * @param task 定时任务
         */
        public void scheduleJob(Task task) throws SchedulerException, ClassNotFoundException {
            //定时任务的名字和组名
            JobKey jobKey = task.getJobKey();
            //定时任务的元数据
            JobDataMap jobDataMap = getJobDataMap(task.getJobDataMap());
            //定时任务的描述
            String desc = task.getDesc();
            //定时任务的逻辑实现类
            Class<? extends Job> jobClass = (Class<? extends Job>) Class.forName(task.getJobClass());
            //定时任务的cron表达式
            String cron = task.getCron();
    
            JobDetail jobDetail = getJobDetail(jobKey, desc, jobDataMap, jobClass);
            Trigger trigger = getTrigger(jobKey, desc, jobDataMap, cron);
            scheduler.scheduleJob(jobDetail, trigger);
        }
    
        /**
         * 暂停Job
         * {@link org.quartz.Scheduler#pauseJob(JobKey)}
         */
        public void pauseJob(JobKey jobKey) throws SchedulerException {
            scheduler.pauseJob(jobKey);
        }
    
        /**
         * 恢复Job
         * {@link org.quartz.Scheduler#resumeJob(JobKey)}
         */
        public void resumeJob(JobKey jobKey) throws SchedulerException {
            scheduler.resumeJob(jobKey);
        }
    
        /**
         * 删除Job
         * {@link org.quartz.Scheduler#deleteJob(JobKey)}
         */
        public void deleteJob(JobKey jobKey) throws SchedulerException {
            scheduler.deleteJob(jobKey);
        }
    
        /**
         * 修改Job的cron表达式
         */
        public boolean modifyJobCron(Task task) {
            String cron = task.getCron();
            //如果cron表达式的格式不正确,则返回修改失败
            if (!CronExpression.isValidExpression(cron)) return false;
            JobKey jobKey = task.getJobKey();
            TriggerKey triggerKey = new TriggerKey(jobKey.getName(), jobKey.getGroup());
            try {
                CronTrigger cronTrigger = (CronTrigger) scheduler.getTrigger(triggerKey);
                JobDataMap jobDataMap = getJobDataMap(task.getJobDataMap());
                //如果cron发生变化了,则按新cron触发 进行重新启动定时任务
                if (!cronTrigger.getCronExpression().equalsIgnoreCase(cron)) {
                    CronTrigger trigger = TriggerBuilder.newTrigger()
                            .withIdentity(triggerKey)
                            .withSchedule(CronScheduleBuilder.cronSchedule(cron))
                            .usingJobData(jobDataMap)
                            .build();
                    scheduler.rescheduleJob(triggerKey, trigger);
                }
            } catch (SchedulerException e) {
                log.error("printStackTrace", e);
                return false;
            }
            return true;
        }
    
        /**
         * 获取定时任务的定义
         * JobDetail是任务的定义,Job是任务的执行逻辑
         *
         * @param jobKey      任务的名称和组名
         * @param desc        任务的描述
         * @param jobDataMap  任务的元数据
         * @param jobClass    {@link org.quartz.Job} 定时任务的 真正执行逻辑定义类
         */
        public JobDetail getJobDetail(JobKey jobKey, String desc, JobDataMap jobDataMap, Class<? extends Job> jobClass) {
            return JobBuilder.newJob(jobClass)
                    .withIdentity(jobKey)
                    .withDescription(desc)
                    .setJobData(jobDataMap)
                    .usingJobData(jobDataMap)
                    .requestRecovery()
                    .storeDurably()
                    .build();
        }
    
        /**
         * 获取Trigger (Job的触发器,执行规则)
         *
         * @param jobKey         任务的名称和组名
         * @param description    任务的描述
         * @param jobDataMap     任务的元数据
         * @param cronExpression 任务的执行cron表达式
         */
        public Trigger getTrigger(JobKey jobKey, String description, JobDataMap jobDataMap, String cronExpression) {
            return TriggerBuilder.newTrigger()
                    .withIdentity(jobKey.getName(), jobKey.getGroup())
                    .withDescription(description)
                    .withSchedule(CronScheduleBuilder.cronSchedule(cronExpression))
                    .usingJobData(jobDataMap)
                    .build();
        }
    
        public JobDataMap getJobDataMap(Map<?, ?> map) {
            return map == null ? new JobDataMap() : new JobDataMap(map);
        }
    
    }
    

    编写执行任务的逻辑类JobOne存放与jobs包下:

    /**
     * @ClassName JobOne
     * @Description 定时任务的具体执行逻辑类
     */
    @Slf4j
    @DisallowConcurrentExecution
    public class JobOne implements Job {
    
        @Override
        public void execute(JobExecutionContext jobExecutionContext) throws JobExecutionException {
            String time = LocalDateTime.ofInstant(new Date().toInstant(),
                    ZoneId.systemDefault()).
                    format(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss"));
            log.info(time.concat("JobOne.execute"));
    
            //获取JobDataMap
            JobDataMap jobDataMap =  jobExecutionContext.getJobDetail().getJobDataMap();
            log.info(jobDataMap.getString("userName"));
            log.info(jobDataMap.getString("passWord"));
        }
    }
    

    编写JobController存放与controller包下:

    /**
     * @ClassName JobController
     * @Description JobController
     */
    @RestController
    public class JobController {
    
        @Autowired
        QuartzJobService quartzJobService;
    
        //创建&启动
        @PostMapping("startJob")
        public String startJob(@RequestBody Task task) throws SchedulerException, ClassNotFoundException {
            quartzJobService.scheduleJob(task);
            return "startJob Success!";
        }
    
        //暂停
        @PostMapping("pauseJob")
        public String pauseJob(@RequestBody Task task)throws SchedulerException{
            quartzJobService.pauseJob(task.getJobKey());
            return "pauseJob Success!";
        }
    
        //恢复
        @PostMapping("resumeJob")
        public String resumeJob(@RequestBody Task task)throws SchedulerException{
            quartzJobService.resumeJob(task.getJobKey());
            return "resumeJob Success!";
        }
    
        //删除
        @PostMapping("delJob")
        public String delJob(@RequestBody Task task)throws SchedulerException{
            quartzJobService.deleteJob(task.getJobKey());
            return "delJob Success!";
        }
    
        //修改
        @PostMapping("modifyJob")
        public String modifyJob(@RequestBody Task task){
            if(quartzJobService.modifyJobCron(task)){
                return "modifyJob Success!";
            }else{
                return "modifyJob Fail!";
            }
        }
    }
    

    启动项目,使用postman工具请求接口,创建一个任务,观察控制台:
    请求接口:http://localhost:8080/startJob
    在这里插入图片描述
    上面的任务每5秒执行一次,控制台打印如下:
    在这里插入图片描述
    点击这里获取示例源码

    展开全文
  • 三种定时任务的比较

    2016-05-30 19:35:50
    综观目前的 Web 应用,多数应用都具备任务调度的功能。本文由浅入深介绍了几种任务调度的 Java 实现方法,包括 Timer,Scheduler, Quartz 以及 JCron Tab,并对其优缺点进行比较,目的在于给需要开发任务调度的...

    综观目前的 Web 应用,多数应用都具备任务调度的功能。本文由浅入深介绍了几种任务调度的 Java 实现方法,包括 Timer,Scheduler, Quartz 以及 JCron Tab,并对其优缺点进行比较,目的在于给需要开发任务调度的程序员提供有价值的参考。

     

    任务调度是指基于给定时间点,给定时间间隔或者给定执行次数自动执行任务。本文由浅入深介绍四种任务调度的 Java 实现:

    • Timer
    • ScheduledExecutor
    • 开源工具包 Quartz
    • 开源工具包 JCronTab

    此外,为结合实现复杂的任务调度,本文还将介绍 Calendar 的一些使用方法。

    Timer

    相信大家都已经非常熟悉 java.util.Timer 了,它是最简单的一种实现任务调度的方法,下面给出一个具体的例子:

    复制代码
     package com.ibm.scheduler; 
     import java.util.Timer; 
     import java.util.TimerTask; 
    
     public class TimerTest extends TimerTask { 
    
     private String jobName = ""; 
    
     public TimerTest(String jobName) { 
     super(); 
     this.jobName = jobName; 
     } 
    
     @Override 
     public void run() { 
     System.out.println("execute " + jobName); 
     } 
    
     public static void main(String[] args) { 
     Timer timer = new Timer(); 
     long delay1 = 1 * 1000; 
     long period1 = 1000; 
     // 从现在开始 1 秒钟之后,每隔 1 秒钟执行一次 job1 
     timer.schedule(new TimerTest("job1"), delay1, period1); 
     long delay2 = 2 * 1000; 
     long period2 = 2000; 
     // 从现在开始 2 秒钟之后,每隔 2 秒钟执行一次 job2 
     timer.schedule(new TimerTest("job2"), delay2, period2); 
     } 
     } 
    复制代码

    /**
    输出结果: 
    execute job1 
    execute job1 
    execute job2 
    execute job1 
    execute job1 
    execute job2 
    */

     

    使用 Timer 实现任务调度的核心类是 Timer 和 TimerTask。其中 Timer 负责设定 TimerTask 的起始与间隔执行时间。使用者只需要创建一个 TimerTask 的继承类,实现自己的 run 方法,然后将其丢给 Timer 去执行即可。

    Timer 的设计核心是一个 TaskList 和一个 TaskThread。Timer 将接收到的任务丢到自己的 TaskList 中,TaskList 按照 Task 的最初执行时间进行排序。TimerThread 在创建 Timer 时会启动成为一个守护线程。这个线程会轮询所有任务,找到一个最近要执行的任务,然后休眠,当到达最近要执行任务的开始时间点,TimerThread 被唤醒并执行该任务。之后 TimerThread 更新最近一个要执行的任务,继续休眠。

    Timer 的优点在于简单易用,但由于所有任务都是由同一个线程来调度,因此所有任务都是串行执行的,同一时间只能有一个任务在执行,前一个任务的延迟或异常都将会影响到之后的任务。

     

     

    ScheduledExecutor

    鉴于 Timer 的上述缺陷,Java 5 推出了基于线程池设计的 ScheduledExecutor。其设计思想是,每一个被调度的任务都会由线程池中一个线程去执行,因此任务是并发执行的,相互之间不会受到干扰。需 要注意的是,只有当任务的执行时间到来时,ScheduedExecutor 才会真正启动一个线程,其余时间 ScheduledExecutor 都是在轮询任务的状态。

    复制代码
    package com.ibm.scheduler;
    import java.util.concurrent.Executors;
    import java.util.concurrent.ScheduledExecutorService;
    import java.util.concurrent.TimeUnit;
    
    public class ScheduledExecutorTest implements Runnable {
        private String jobName = "";
    
        public ScheduledExecutorTest(String jobName) {
            super();
            this.jobName = jobName;
        }
    
        @Override
        public void run() {
            System.out.println("execute " + jobName);
        }
    
        public static void main(String[] args) {
            ScheduledExecutorService service = Executors.newScheduledThreadPool(10);
    
            long initialDelay1 = 1;
            long period1 = 1;
            // 从现在开始1秒钟之后,每隔1秒钟执行一次job1
            service.scheduleAtFixedRate(
                    new ScheduledExecutorTest("job1"), initialDelay1,
                    period1, TimeUnit.SECONDS);
    
            long initialDelay2 = 1;
            long delay2 = 1;
            // 从现在开始2秒钟之后,每隔2秒钟执行一次job2
            service.scheduleWithFixedDelay(
                    new ScheduledExecutorTest("job2"), initialDelay2,
                    delay2, TimeUnit.SECONDS);
        }
    }
    复制代码

    /**
    输出结果:
    execute job1
    execute job1
    execute job2
    execute job1
    execute job1
    execute job2
    */

     

    上面代码展示了 ScheduledExecutorService 中两种最常用的调度方法 ScheduleAtFixedRate 和 ScheduleWithFixedDelay。ScheduleAtFixedRate 每次执行时间为上一次任务开始起向后推一个时间间隔,即每次执行时间为 :initialDelay, initialDelay+period, initialDelay+2*period, …;ScheduleWithFixedDelay 每次执行时间为上一次任务结束起向后推一个时间间隔,即每次执行时间为:initialDelay, initialDelay+executeTime+delay, initialDelay+2*executeTime+2*delay。由此可见,ScheduleAtFixedRate 是基于固定时间间隔进行任务调度,ScheduleWithFixedDelay 取决于每次任务执行的时间长短,是基于不固定时间间隔进行任务调度。  

     

    用 ScheduledExecutor 和 Calendar 实现复杂任务调度

    Timer 和 ScheduledExecutor 都仅能提供基于开始时间与重复间隔的任务调度,不能胜任更加复杂的调度需求。比如,设置每星期二的 16:38:10 执行任务。该功能使用 Timer 和 ScheduledExecutor 都不能直接实现,但我们可以借助 Calendar 间接实现该功能。

    复制代码
    package com.ibm.scheduler;
    
    import java.util.Calendar;
    import java.util.Date;
    import java.util.TimerTask;
    import java.util.concurrent.Executors;
    import java.util.concurrent.ScheduledExecutorService;
    import java.util.concurrent.TimeUnit;
    
    public class ScheduledExceutorTest2 extends TimerTask {
    
        private String jobName = "";
    
        public ScheduledExceutorTest2(String jobName) {
            super();
            this.jobName = jobName;
        }
    
        @Override
        public void run() {
            System.out.println("Date = "+new Date()+", execute " + jobName);
        }
    
        /**
         * 计算从当前时间currentDate开始,满足条件dayOfWeek, hourOfDay, 
         * minuteOfHour, secondOfMinite的最近时间
         * @return
         */
        public Calendar getEarliestDate(Calendar currentDate, int dayOfWeek,
                int hourOfDay, int minuteOfHour, int secondOfMinite) {
            //计算当前时间的WEEK_OF_YEAR,DAY_OF_WEEK, HOUR_OF_DAY, MINUTE,SECOND等各个字段值
            int currentWeekOfYear = currentDate.get(Calendar.WEEK_OF_YEAR);
            int currentDayOfWeek = currentDate.get(Calendar.DAY_OF_WEEK);
            int currentHour = currentDate.get(Calendar.HOUR_OF_DAY);
            int currentMinute = currentDate.get(Calendar.MINUTE);
            int currentSecond = currentDate.get(Calendar.SECOND);
    
            //如果输入条件中的dayOfWeek小于当前日期的dayOfWeek,则WEEK_OF_YEAR需要推迟一周
            boolean weekLater = false;
            if (dayOfWeek < currentDayOfWeek) {
                weekLater = true;
            } else if (dayOfWeek == currentDayOfWeek) {
                //当输入条件与当前日期的dayOfWeek相等时,如果输入条件中的
                //hourOfDay小于当前日期的
                //currentHour,则WEEK_OF_YEAR需要推迟一周    
                if (hourOfDay < currentHour) {
                    weekLater = true;
                } else if (hourOfDay == currentHour) {
                     //当输入条件与当前日期的dayOfWeek, hourOfDay相等时,
                     //如果输入条件中的minuteOfHour小于当前日期的
                    //currentMinute,则WEEK_OF_YEAR需要推迟一周
                    if (minuteOfHour < currentMinute) {
                        weekLater = true;
                    } else if (minuteOfHour == currentSecond) {
                         //当输入条件与当前日期的dayOfWeek, hourOfDay, 
                         //minuteOfHour相等时,如果输入条件中的
                        //secondOfMinite小于当前日期的currentSecond,
                        //则WEEK_OF_YEAR需要推迟一周
                        if (secondOfMinite < currentSecond) {
                            weekLater = true;
                        }
                    }
                }
            }
            if (weekLater) {
                //设置当前日期中的WEEK_OF_YEAR为当前周推迟一周
                currentDate.set(Calendar.WEEK_OF_YEAR, currentWeekOfYear + 1);
            }
            // 设置当前日期中的DAY_OF_WEEK,HOUR_OF_DAY,MINUTE,SECOND为输入条件中的值。
            currentDate.set(Calendar.DAY_OF_WEEK, dayOfWeek);
            currentDate.set(Calendar.HOUR_OF_DAY, hourOfDay);
            currentDate.set(Calendar.MINUTE, minuteOfHour);
            currentDate.set(Calendar.SECOND, secondOfMinite);
            return currentDate;
    
        }
    
        public static void main(String[] args) throws Exception {
    
            ScheduledExceutorTest2 test = new ScheduledExceutorTest2("job1");
            //获取当前时间
            Calendar currentDate = Calendar.getInstance();
            long currentDateLong = currentDate.getTime().getTime();
            System.out.println("Current Date = " + currentDate.getTime().toString());
            //计算满足条件的最近一次执行时间
            Calendar earliestDate = test
                    .getEarliestDate(currentDate, 3, 16, 38, 10);
            long earliestDateLong = earliestDate.getTime().getTime();
            System.out.println("Earliest Date = "
                    + earliestDate.getTime().toString());
            //计算从当前时间到最近一次执行时间的时间间隔
            long delay = earliestDateLong - currentDateLong;
            //计算执行周期为一星期
            long period = 7 * 24 * 60 * 60 * 1000;
            ScheduledExecutorService service = Executors.newScheduledThreadPool(10);
            //从现在开始delay毫秒之后,每隔一星期执行一次job1
            service.scheduleAtFixedRate(test, delay, period,
                    TimeUnit.MILLISECONDS);
    
        }
    } 
    复制代码

     

    1
    2
    3
    4
    5
    6
    7
    /**
    输出结果:
    Current Date = Wed Feb 02 17:32:01 CST 2011
    Earliest Date = Tue Feb 8 16:38:10 CST 2011
    Date = Tue Feb 8 16:38:10 CST 2011, execute job1
    Date = Tue Feb 15 16:38:10 CST 2011, execute job1
    */

     

    清单 3 实现了每星期二 16:38:10 调度任务的功能。其核心在于根据当前时间推算出最近一个星期二 16:38:10 的绝对时间,然后计算与当前时间的时间差,作为调用 ScheduledExceutor 函数的参数。计算最近时间要用到 java.util.calendar 的功能。首先需要解释 calendar 的一些设计思想。Calendar 有以下几种唯一标识一个日期的组合方式:

     YEAR + MONTH + DAY_OF_MONTH 
     YEAR + MONTH + WEEK_OF_MONTH + DAY_OF_WEEK 
     YEAR + MONTH + DAY_OF_WEEK_IN_MONTH + DAY_OF_WEEK 
     YEAR + DAY_OF_YEAR 
     YEAR + DAY_OF_WEEK + WEEK_OF_YEAR 

     

    上述组合分别加上 HOUR_OF_DAY + MINUTE + SECOND 即为一个完整的时间标识。本例采用了最后一种组合方式。输入为 DAY_OF_WEEK, HOUR_OF_DAY, MINUTE, SECOND 以及当前日期 , 输出为一个满足 DAY_OF_WEEK, HOUR_OF_DAY, MINUTE, SECOND 并且距离当前日期最近的未来日期。计算的原则是从输入的 DAY_OF_WEEK 开始比较,如果小于当前日期的 DAY_OF_WEEK,则需要向 WEEK_OF_YEAR 进一, 即将当前日期中的 WEEK_OF_YEAR 加一并覆盖旧值;如果等于当前的 DAY_OF_WEEK, 则继续比较 HOUR_OF_DAY;如果大于当前的 DAY_OF_WEEK,则直接调用 java.util.calenda 的 calendar.set(field, value) 函数将当前日期的 DAY_OF_WEEK, HOUR_OF_DAY, MINUTE, SECOND 赋值为输入值,依次类推,直到比较至 SECOND。读者可以根据输入需求选择不同的组合方式来计算最近执行时间。

    可以看出,用上述方法实现该任务调度比较麻烦,这就需要一个更加完善的任务调度框架来解决这些复杂的调度问题。幸运的是,开源工具包 Quartz 与 JCronTab 提供了这方面强大的支持。

      /**
    * 定时任务
    */
    private void intScheduledCache(){
    scheduledExecutorService = Executors.newScheduledThreadPool(4);

    /*加载字典信息*/
    scheduledExecutorService.scheduleWithFixedDelay(new Runnable() {
    @Override
    public void run() {
    List<DictInfo> list = dictInfoDao.selectAll();
    for (DictInfo info : list) {
    ID2MODEL_CACHE.put(info.getId(), info);
    }
    LOG.info("ID2MODEL_CACHE size:{}", ID2MODEL_CACHE.size());
    }
    }, 0, SCHEDULE_TIME, TimeUnit.MINUTES);

    /*加载type的id值*/
    scheduledExecutorService.scheduleWithFixedDelay(new Runnable() {
    @Override
    public void run() {
    TYPE_ID = dictInfoDao.selectTypeIds();
    LOG.info("TYPE_ID : {}", TYPE_ID);
    }
    }, 0, SCHEDULE_TIME, TimeUnit.MINUTES);

    LOG.info("intScheduledCache is over..");

    /*SHIELD_ID定时清空*/
    scheduledExecutorService.scheduleWithFixedDelay(new Runnable() {
    @Override
    public void run() {
    LOG.info("SHIELD_ID list:{}", SHIELD_ID);
    SHIELD_ID.clear();
    LOG.info("SHIELD_ID clear..");
    }
    }, 0, SCHEDULE_TIME, TimeUnit.MINUTES);

    /*电视台信息定时加载*/
    scheduledExecutorService.scheduleWithFixedDelay(new Runnable() {
    @Override
    public void run() {
    List<TvDictInfo> list = tvInfoDao.selectAll();
    for (TvDictInfo info : list) {
    TV_CACHE.put(info.getId(), info);
    }
    LOG.info("Reload tv list size:{}", list.size());
    }
    }, 0, SCHEDULE_TIME, TimeUnit.MINUTES);
    }

     

    Quartz

    Quartz 可以满足更多更复杂的调度需求,首先让我们看看如何用 Quartz 实现每星期二 16:38 的调度安排:

    复制代码
    package com.ibm.scheduler;
    import java.util.Date;
    
    import org.quartz.Job;
    import org.quartz.JobDetail;
    import org.quartz.JobExecutionContext;
    import org.quartz.JobExecutionException;
    import org.quartz.Scheduler;
    import org.quartz.SchedulerFactory;
    import org.quartz.Trigger;
    import org.quartz.helpers.TriggerUtils;
    
    public class QuartzTest implements Job {
    
        @Override
        //该方法实现需要执行的任务
        public void execute(JobExecutionContext arg0) throws JobExecutionException {
            System.out.println("Generating report - "
                    + arg0.getJobDetail().getFullName() + ", type ="
                    + arg0.getJobDetail().getJobDataMap().get("type"));
            System.out.println(new Date().toString());
        }
        public static void main(String[] args) {
            try {
                // 创建一个Scheduler
                SchedulerFactory schedFact = 
                new org.quartz.impl.StdSchedulerFactory();
                Scheduler sched = schedFact.getScheduler();
                sched.start();
                // 创建一个JobDetail,指明name,groupname,以及具体的Job类名,
                //该Job负责定义需要执行任务
                JobDetail jobDetail = new JobDetail("myJob", "myJobGroup",
                        QuartzTest.class);
                jobDetail.getJobDataMap().put("type", "FULL");
                // 创建一个每周触发的Trigger,指明星期几几点几分执行
                Trigger trigger = TriggerUtils.makeWeeklyTrigger(3, 16, 38);
                trigger.setGroup("myTriggerGroup");
                // 从当前时间的下一秒开始执行
                trigger.setStartTime(TriggerUtils.getEvenSecondDate(new Date()));
                // 指明trigger的name
                trigger.setName("myTrigger");
                // 用scheduler将JobDetail与Trigger关联在一起,开始调度任务
                sched.scheduleJob(jobDetail, trigger);
    
            } catch (Exception e) {
                e.printStackTrace();
            }
        }
    } 
    复制代码

    /**
    输出结果:
    Generating report - myJobGroup.myJob, type =FULL
    Tue Feb 8 16:38:00 CST 2011
    Generating report - myJobGroup.myJob, type =FUL
    Tue Feb 15 16:38:00 CST 2011
    */

     

    清单 4 非常简洁地实现了一个上述复杂的任务调度。Quartz 设计的核心类包括 Scheduler, Job 以及 Trigger。其中,Job 负责定义需要执行的任务,Trigger 负责设置调度策略,Scheduler 将二者组装在一起,并触发任务开始执行。

    Job

    使用者只需要创建一个 Job 的继承类,实现 execute 方法。JobDetail 负责封装 Job 以及 Job 的属性,并将其提供给 Scheduler 作为参数。每次 Scheduler 执行任务时,首先会创建一个 Job 的实例,然后再调用 execute 方法执行。Quartz 没有为 Job 设计带参数的构造函数,因此需要通过额外的 JobDataMap 来存储 Job 的属性。JobDataMap 可以存储任意数量的 Key,Value 对,例如:

    jobDetail.getJobDataMap().put("myDescription", "my job description"); 
    jobDetail.getJobDataMap().put("myValue", 1998); 
    ArrayList<String> list = new ArrayList<String>(); 
    list.add("item1"); 
    jobDetail.getJobDataMap().put("myArray", list); 

     

     JobDataMap 中的数据可以通过下面的方式获取:

     

    复制代码
    public class JobDataMapTest implements Job {
    
        @Override
        public void execute(JobExecutionContext context)
                throws JobExecutionException {
            //从context中获取instName,groupName以及dataMap
            String instName = context.getJobDetail().getName();
            String groupName = context.getJobDetail().getGroup();
            JobDataMap dataMap = context.getJobDetail().getJobDataMap();
            //从dataMap中获取myDescription,myValue以及myArray
            String myDescription = dataMap.getString("myDescription");
            int myValue = dataMap.getInt("myValue");
            ArrayList<String> myArray = (ArrayListlt;Strin>) dataMap.get("myArray");
            System.out.println("
                    Instance =" + instName + ", group = " + groupName
                    + ", description = " + myDescription + ", value =" + myValue
                    + ", array item0 = " + myArray.get(0));
    
        }
    }
    复制代码

     

     

     

    1
    2
    3
    4
    5
    6
    /**
    输出结果:
    Instance = myJob, group = myJobGroup,
    description = my job description,
    value =1998, array item0 = item1
    */

     

    Trigger

    Trigger 的作用是设置调度策略。Quartz 设计了多种类型的 Trigger,其中最常用的是 SimpleTrigger 和 CronTrigger。

    SimpleTrigger 适用于在某一特定的时间执行一次,或者在某一特定的时间以某一特定时间间隔执行多次。上述功能决定了 SimpleTrigger 的参数包括 start-time, end-time, repeat count, 以及 repeat interval。

    Repeat count 取值为大于或等于零的整数,或者常量 SimpleTrigger.REPEAT_INDEFINITELY。

    Repeat interval 取值为大于或等于零的长整型。当 Repeat interval 取值为零并且 Repeat count 取值大于零时,将会触发任务的并发执行。

    Start-time 与 dnd-time 取值为 java.util.Date。当同时指定 end-time 与 repeat count 时,优先考虑 end-time。一般地,可以指定 end-time,并设定 repeat count 为 REPEAT_INDEFINITELY。

    以下是 SimpleTrigger 的构造方法:

     public SimpleTrigger(String name, 
                           String group, 
                           Date startTime, 
                           Date endTime, 
                           int repeatCount, 
                           long repeatInterval) 

     

    举例如下:

    创建一个立即执行且仅执行一次的 SimpleTrigger:

    1
    2
    SimpleTrigger trigger=
     new SimpleTrigger("myTrigger", "myGroup", new Date(), null, 0, 0L);

    创建一个半分钟后开始执行,且每隔一分钟重复执行一次的 SimpleTrigger:

    1
    2
    3
    SimpleTrigger trigger=
     new SimpleTrigger("myTrigger", "myGroup",
        new Date(System.currentTimeMillis()+30*1000), null, 0, 60*1000);

    创建一个 2011 年 6 月 1 日 8:30 开始执行,每隔一小时执行一次,一共执行一百次,一天之后截止的 SimpleTrigger:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    Calendar calendar = Calendar.getInstance();
    calendar.set(Calendar.YEAR, 2011);
    calendar.set(Calendar.MONTH, Calendar.JUNE);
    calendar.set(Calendar.DAY_OF_MONTH, 1);
    calendar.set(Calendar.HOUR, 8);
    calendar.set(Calendar.MINUTE, 30);
    calendar.set(Calendar.SECOND, 0);
    calendar.set(Calendar.MILLISECOND, 0);
    Date startTime = calendar.getTime();
    Date endTime = new Date (calendar.getTimeInMillis() +24*60*60*1000);
    SimpleTrigger trigger=new SimpleTrigger("myTrigger",
           "myGroup", startTime, endTime, 100, 60*60*1000);

    上述最后一个例子中,同时设置了 end-time 与 repeat count,则优先考虑 end-time,总共可以执行二十四次。

    CronTrigger 的用途更广,相比基于特定时间间隔进行调度安排的 SimpleTrigger,CronTrigger 主要适用于基于日历的调度安排。例如:每星期二的 16:38:10 执行,每月一号执行,以及更复杂的调度安排等。

    CronTrigger 同样需要指定 start-time 和 end-time,其核心在于 Cron 表达式,由七个字段组成:

     Seconds 
     Minutes 
     Hours 
     Day-of-Month 
     Month 
     Day-of-Week 
     Year (Optional field) 

     

    举例如下:

    创建一个每三小时执行的 CronTrigger,且从每小时的整点开始执行:

     0 0 0/3  * * ? 

     

    创建一个每十分钟执行的 CronTrigger,且从每小时的第三分钟开始执行:

     0 3/10 * * * ? 

     

    创建一个每周一,周二,周三,周六的晚上 20:00 到 23:00,每半小时执行一次的 CronTrigger:

     0 0/30 20-23 ? * MON-WED,SAT 

     

    创建一个每月最后一个周四,中午 11:30-14:30,每小时执行一次的 trigger:

     0 30 11-14/1 ? * 5L 

     

    解释一下上述例子中各符号的含义:

    首先所有字段都有自己特定的取值,例如,Seconds 和 Minutes 取值为 0 到 59,Hours 取值为 0 到 23,Day-of-Month 取值为 0-31, Month 取值为 0-11,或者 JAN,FEB, MAR, APR, MAY, JUN, JUL, AUG, SEP, OCT, NOV, DEC,Days-of-Week 取值为 1-7 或者 SUN, MON, TUE, WED, THU, FRI, SAT。每个字段可以取单个值,多个值,或一个范围,例如 Day-of-Week 可取值为“MON,TUE,SAT”,“MON-FRI”或者“TUE-THU,SUN”。

    通配符 * 表示该字段可接受任何可能取值。例如 Month 字段赋值 * 表示每个月,Day-of-Week 字段赋值 * 表示一周的每天。

    / 表示开始时刻与间隔时段。例如 Minutes 字段赋值 2/10 表示在一个小时内每 20 分钟执行一次,从第 2 分钟开始。

    ? 仅适用于 Day-of-Month 和 Day-of-Week。? 表示对该字段不指定特定值。适用于需要对这两个字段中的其中一个指定值,而对另一个不指定值的情况。一般情况下,这两个字段只需对一个赋值。

    L 仅适用于 Day-of-Month 和 Day-of-Week。L 用于 Day-of-Month 表示该月最后一天。L 单独用于 Day-of-Week 表示周六,否则表示一个月最后一个星期几,例如 5L 或者 THUL 表示该月最后一个星期四。

    W 仅适用于 Day-of-Month,表示离指定日期最近的一个工作日,例如 Day-of-Month 赋值为 10W 表示该月离 10 号最近的一个工作日。

    # 仅适用于 Day-of-Week,表示该月第 XXX 个星期几。例如 Day-of-Week 赋值为 5#2 或者 THU#2,表示该月第二个星期四。

    CronTrigger 的使用如下:

     CronTrigger cronTrigger = new CronTrigger("myTrigger", "myGroup"); 
     try { 
         cronTrigger.setCronExpression("0 0/30 20-13 ? * MON-WED,SAT"); 
     } catch (Exception e) { 
         e.printStackTrace(); 
     } 

     

    Job 与 Trigger 的松耦合设计是 Quartz 的一大特点,其优点在于同一个 Job 可以绑定多个不同的 Trigger,同一个 Trigger 也可以调度多个 Job,灵活性很强。

    Listener

    除了上述基本的调度功能,Quartz 还提供了 listener 的功能。主要包含三种 listener:JobListener,TriggerListener 以及 SchedulerListener。当系统发生故障,相关人员需要被通知时,Listener 便能发挥它的作用。最常见的情况是,当任务被执行时,系统发生故障,Listener 监听到错误,立即发送邮件给管理员。下面给出 JobListener 的实例:


    清单 7. JobListener 的实现 

    				 
     import org.quartz.JobExecutionContext;
    import org.quartz.JobExecutionException;
    import org.quartz.JobListener;
    import org.quartz.SchedulerException;
    
    
    public class MyListener implements JobListener{
    
    	@Override
    	public String getName() {
    		return "My Listener";
    	}
    	@Override
    	public void jobWasExecuted(JobExecutionContext context,
    			JobExecutionException jobException) {
    		if(jobException != null){
    			try {
    				//停止Scheduler
    				context.getScheduler().shutdown();
    				System.out.println("
                    Error occurs when executing jobs, shut down the scheduler ");
                    // 给管理员发送邮件…
    			} catch (SchedulerException e) {
    				e.printStackTrace();
    			}
    		}
    	}
    }

     

    从清单 7 可以看出,使用者只需要创建一个 JobListener 的继承类,重载需要触发的方法即可。当然,需要将 listener 的实现类注册到 Scheduler 和 JobDetail 中:

     sched.addJobListener(new MyListener()); 
     jobDetail.addJobListener("My Listener"); // listener 的名字

     

    使用者也可以将 listener 注册为全局 listener,这样便可以监听 scheduler 中注册的所有任务 :

     sched.addGlobalJobListener(new MyListener()); 

     

    为了测试 listener 的功能,可以在 job 的 execute 方法中强制抛出异常。清单 7 中,listener 接收到异常,将 job 所在的 scheduler 停掉,阻止后续的 job 继续执行。scheduler、jobDetail 等信息都可以从 listener 的参数 context 中检索到。

    清单 7 的输出结果为:

     Generating report - myJob.myJob, type =FULL 
     Tue Feb 15 18:57:35 CST 2011 
     2011-2-15 18:57:35 org.quartz.core.JobRunShell run 
    信息 : Job myJob.myJob threw a JobExecutionException: 
     org.quartz.JobExecutionException 
     at com.ibm.scheduler.QuartzListenerTest.execute(QuartzListenerTest.java:22) 
     at org.quartz.core.JobRunShell.run(JobRunShell.java:191) 
     at org.quartz.simpl.SimpleThreadPool$WorkerThread.run(SimpleThreadPool.java:516) 
     2011-2-15 18:57:35 org.quartz.core.QuartzScheduler shutdown 
    信息 : Scheduler DefaultQuartzScheduler_$_NON_CLUSTERED shutting down. 
     Error occurs when executing jobs, shut down the scheduler 

     

    TriggerListener、SchedulerListener 与 JobListener 有类似的功能,只是各自触发的事件不同,如 JobListener 触发的事件为:

    Job to be executed, Job has completed execution 等

    TriggerListener 触发的事件为:

    Trigger firings, trigger mis-firings, trigger completions 等

    SchedulerListener 触发的事件为:

    add a job/trigger, remove a job/trigger, shutdown a scheduler 等

    读者可以根据自己的需求重载相应的事件。

    JobStores

    Quartz 的另一显著优点在于持久化,即将任务调度的相关数据保存下来。这样,当系统重启后,任务被调度的状态依然存在于系统中,不会丢失。默认情况 下,Quartz 采用的是 org.quartz.simpl.RAMJobStore,在这种情况下,数据仅能保存在内存中,系统重启后会全部丢失。若想持久化数据,需要采用 org.quartz.simpl.JDBCJobStoreTX。

    实现持久化的第一步,是要创建 Quartz 持久化所需要的表格。在 Quartz 的发布包 docs/dbTables 中可以找到相应的表格创建脚本。Quartz 支持目前大部分流行的数据库。本文以 DB2 为例,所需要的脚本为 tables_db2.sql。首先需要对脚本做一点小的修改,即在开头指明 Schema:

     SET CURRENT SCHEMA quartz; 

     

    为了方便重复使用 , 创建表格前首先删除之前的表格:
    drop table qrtz_job_details;

     drop table qrtz_job_listeners; 

     

    然后创建数据库 sched,执行 tables_db2.sql 创建持久化所需要的表格。

    第二步,配置数据源。数据源与其它所有配置,例如 ThreadPool,均放在 quartz.properties 里:


    清单 8. Quartz 配置文件 

    				 
     # Configure ThreadPool 
     org.quartz.threadPool.class = org.quartz.simpl.SimpleThreadPool 
     org.quartz.threadPool.threadCount =  5 
     org.quartz.threadPool.threadPriority = 4 
    
     # Configure Datasources 
     org.quartz.jobStore.class = org.quartz.impl.jdbcjobstore.JobStoreTX 
     org.quartz.jobStore.driverDelegateClass = org.quartz.impl.jdbcjobstore.StdJDBCDelegate 
     org.quartz.jobStore.dataSource = db2DS 
     org.quartz.jobStore.tablePrefix = QRTZ_ 
    
     org.quartz.dataSource.db2DS.driver = com.ibm.db2.jcc.DB2Driver 
     org.quartz.dataSource.db2DS.URL = jdbc:db2://localhost:50001/sched 
     org.quartz.dataSource.db2DS.user = quartz 
     org.quartz.dataSource.db2DS.password = passw0rd 
     org.quartz.dataSource.db2DS.maxConnections = 5 

     

    使用时只需要将 quatz.properties 放在 classpath 下面,不用更改一行代码,再次运行之前的任务调度实例,trigger、job 等信息便会被记录在数据库中。

    将清单 4 中的 makeWeeklyTrigger 改成 makeSecondlyTrigger,重新运行 main 函数,在 sched 数据库中查询表 qrtz_simple_triggers 中的数据。其查询语句为“db2 ‘ select repeat_interval, times_triggered from qrtz_simple_triggers ’”。结果 repeat_interval 为 1000,与程序中设置的 makeSecondlyTrigger 相吻合,times_triggered 值为 21。

    停掉程序,将数据库中记录的任务调度数据重新导入程序运行:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    package com.ibm.scheduler;
    import org.quartz.Scheduler;
    import org.quartz.SchedulerException;
    import org.quartz.SchedulerFactory;
    import org.quartz.Trigger;
    import org.quartz.impl.StdSchedulerFactory;
     
    public class QuartzReschedulerTest {
    public static void main(String[] args) throws SchedulerException {
    // 初始化一个 Schedule Factory
    SchedulerFactory schedulerFactory = new StdSchedulerFactory();
    // 从 schedule factory 中获取 scheduler
    Scheduler scheduler = schedulerFactory.getScheduler();
    // 从 schedule factory 中获取 trigger
    Trigger trigger = scheduler.getTrigger("myTrigger", "myTriggerGroup");
    // 重新开启调度任务
    scheduler.rescheduleJob("myTrigger", "myTriggerGroup", trigger);
    scheduler.start();
    }
    }

     

     

    上面代码中,schedulerFactory.getScheduler() 将 quartz.properties 的内容加载到内存,然后根据数据源的属性初始化数据库的链接,并将数据库中存储的数据加载到内存。之后,便可以在内存中查询某一具体的 trigger,并将其重新启动。这时候重新查询 qrtz_simple_triggers 中的数据,发现 times_triggered 值比原来增长了。

     

     

    JCronTab

    习惯使用 unix/linux 的开发人员应该对 crontab 都不陌生。Crontab 是一个非常方便的用于 unix/linux 系统的任务调度命令。JCronTab 则是一款完全按照 crontab 语法编写的 java 任务调度工具。

    首先简单介绍一下 crontab 的语法,与上面介绍的 Quartz 非常相似,但更加简洁 , 集中了最常用的语法。主要由六个字段组成(括弧中标识了每个字段的取值范围):

     Minutes (0-59)
     Hours   (0-23) 
     Day-of-Month (1-31)
     Month    (1-12/JAN-DEC) 
     Day-of-Week  (0-6/SUN-SAT)
     Command 

     

    与 Quartz 相比,省略了 Seconds 与 Year,多了一个 command 字段,即为将要被调度的命令。JCronTab 中也包含符号“*”与“/”, 其含义与 Quartz 相同。

    举例如下:

    每天 12 点到 15 点 , 每隔 1 小时执行一次 Date 命令:

     0 12-15/1 * * * Date 

     

    每月 2 号凌晨 1 点发一封信给 zhjingbj@cn.ibm.com:

     0 1 2 * * mail -s “good” zhjingbj@cn.ibm.com 

     

    每周一,周二,周三,周六的晚上 20:00 到 23:00,每半小时打印“normal”:

     0/30 20-23 * * MON-WED,SAT echo “normal”

     

    JCronTab 借鉴了 crontab 的语法,其区别在于 command 不再是 unix/linux 的命令,而是一个 Java 类。如果该类带参数,例如“com.ibm.scheduler.JCronTask2#run”,则定期执行 run 方法;如果该类不带参数,则默认执行 main 方法。此外,还可以传参数给 main 方法或者构造函数,例如“com.ibm.scheduler.JCronTask2#run Hello World“表示传两个参数 Hello 和 World 给构造函数。

    JCronTab 与 Quartz 相比,其优点在于,第一,支持多种任务调度的持久化方法,包括普通文件、数据库以及 XML 文件进行持久化;第二,JCronTab 能够非常方便地与 Web 应用服务器相结合,任务调度可以随 Web 应用服务器的启动自动启动;第三,JCronTab 还内置了发邮件功能,可以将任务执行结果方便地发送给需要被通知的人。

    JCronTab 与 Web 应用服务器的结合非常简单,只需要在 Web 应用程序的 web.xml 中添加如下行:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    <servlet>
       <servlet-name>LoadOnStartupServlet</servlet-name>
       <servlet-class>org.jcrontab.web.loadCrontabServlet</servlet-class>
       <init-param>
     <param-name>PROPERTIES_FILE</param-name>
     <param-value>D:/Scheduler/src/jcrontab.properties</param-value>
       </init-param>
       <load-on-startup>1</load-on-startup>
     </servlet>
     <!-- Mapping of the StartUp Servlet -->
     <servlet-mapping>
       <servlet-name>LoadOnStartupServlet</servlet-name>
     <url-pattern>/Startup</url-pattern>
     </servlet-mapping>

     

    在清单 10 中,需要注意两点:第一,必须指定 servlet-class 为 org.jcrontab.web.loadCrontabServlet,因为它是整个任务调度的入口;第二,必须指定一个参数为 PROPERTIES_FILE,才能被 loadCrontabServlet 识别。

    接下来,需要撰写 D:/Scheduler/src/jcrontab.properties 的内容,其内容根据需求的不同而改变。

    当采用普通文件持久化时,jcrontab.properties 的内容主要包括:

     org.jcrontab.data.file = D:/Scheduler/src/crontab 
     org.jcrontab.data.datasource = org.jcrontab.data.FileSource 

     

    其中数据来源 org.jcrontab.data.datasource 被描述为普通文件,即 org.jcrontab.data.FileSource。具体的文件即 org.jcrontab.data.file 指明为 D:/Scheduler/src/crontab。

    Crontab 描述了任务的调度安排:

     */2 * * * * com.ibm.scheduler.JCronTask1 
     * * * * * com.ibm.scheduler.JCronTask2#run Hello World 

     

    其中包含了两条任务的调度,分别是每两分钟执行一次 JCronTask1 的 main 方法,每一分钟执行一次 JCronTask2 的 run 方法。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    package com.ibm.scheduler;
     
    import java.util.Date;
     
    public class JCronTask1 {
     
        private static int count = 0;
     
        public static void main(String[] args) {
            System.out.println("--------------Task1-----------------");
            System.out.println("Current Time = " + new Date() + ", Count = "
                    + count++);
        }
    }
     
    package com.ibm.scheduler;
     
    import java.util.Date;
     
    public class JCronTask2 implements Runnable {
     
        private static int count = 0;
     
        private static String[] args;
     
        public JCronTask2(String[] args) {
            System.out.println("--------------Task2-----------------");
            System.out.println("Current Time = " + new Date() + ", Count = "
                    + count++);
            JCronTask2.args = args;
        }
     
        @Override
        public void run() {
            System.out.println("enter into run method");
            if (args != null && args.length > 0) {
                for (int i = 0; i < args.length; i++) {
             System.out.print("This is arg " + i + " " + args[i] + "\n");
                }
            }
        }
    }

     

     

    到此为止,基于普通文件持久化的 JCronTab 的实例就全部配置好了。启动 Web 应用服务器,便可以看到任务调度的输出结果:
     --------------Task2----------------- 
     Current Time = Tue Feb 15 09:22:00 CST 2011, Count = 0 
     enter into run method 
     This is arg 0 Hello 
     This is arg 1 World 
     --------------Task1----------------- 
     Current Time = Tue Feb 15 09:22:00 CST 2011, Count = 0 
     --------------Task2----------------- 
     Current Time = Tue Feb 15 09:23:00 CST 2011, Count = 1 
     enter into run method 
     This is arg 0 Hello 
     This is arg 1 World 
     --------------Task2----------------- 
     Current Time = Tue Feb 15 09:24:00 CST 2011, Count = 2 
     enter into run method 
     This is arg 0 Hello 
     This is arg 1 World 
     --------------Task1----------------- 
     Current Time = Tue Feb 15 09:24:00 CST 2011, Count = 1 

     

    通过修改 jcrontab.properties 中 datasource,可以选择采用数据库或 xml 文件持久化,感兴趣的读者可以参考 进阶学习 JCronTab

    此外,JCronTab 还内置了发邮件功能,可以将任务执行结果方便地发送给需要被通知的人。其配置非常简单,只需要在 jcontab.properties 中添加几行配置即可:

     org.jcrontab.sendMail.to= Ther email you want to send to 
     org.jcrontab.sendMail.from=The email you want to send from 
     org.jcrontab.sendMail.smtp.host=smtp server 
     org.jcrontab.sendMail.smtp.user=smtp username 
     org.jcrontab.sendMail.smtp.password=smtp password 

    本文介绍了四种常用的对任务进行调度的 Java 实现方法,即 Timer,ScheduledExecutor, Quartz 以及 JCronTab。文本对每种方法都进行了实例解释,并对其优缺点进行比较。对于简单的基于起始时间点与时间间隔的任务调度,使用 Timer 就足够了;如果需要同时调度多个任务,基于线程池的 ScheduledTimer 是更为合适的选择;当任务调度的策略复杂到难以凭借起始时间点与时间间隔来描述时,Quartz 与 JCronTab 则体现出它们的优势。熟悉 Unix/Linux 的开发人员更倾向于 JCronTab,且 JCronTab 更适合与 Web 应用服务器相结合。Quartz 的 Trigger 与 Job 松耦合设计使其更适用于 Job 与 Trigger 的多对多应用场景。

    展开全文
  • Quartz是OpenSymphony开源组织在Job scheduling领域又一个开源项目,完全由Java开发,可以用来执行定时任务,类似于java.util.Timer。但是相较于Timer, Quartz增加了很多功能: 持久性作业 - 就是保持调度...

    一、什么是Quartz

    什么是Quartz?

    Quartz是OpenSymphony开源组织在Job scheduling领域又一个开源项目,完全由Java开发,可以用来执行定时任务,类似于java.util.Timer。但是相较于Timer, Quartz增加了很多功能:

    • 持久性作业 - 就是保持调度定时的状态;
    • 作业管理 - 对调度作业进行有效的管理;

    大部分公司都会用到定时任务这个功能。
    拿火车票购票来说,当你下单后,后台就会插入一条待支付的task(job),一般是30分钟,超过30min后就会执行这个job,去判断你是否支付,未支付就会取消此次订单;当你支付完成之后,后台拿到支付回调后就会再插入一条待消费的task(job),Job触发日期为火车票上的出发日期,超过这个时间就会执行这个job,判断是否使用等。

    在我们实际的项目中,当Job过多的时候,肯定不能人工去操作,这时候就需要一个任务调度框架,帮我们自动去执行这些程序。那么该如何实现这个功能呢?

    (1)首先我们需要定义实现一个定时功能的接口,我们可以称之为Task(或Job),如定时发送邮件的task(Job),重启机器的task(Job),优惠券到期发送短信提醒的task(Job),实现接口如下:
    这里写图片描述

    (2)有了任务之后,还需要一个能够实现触发任务去执行的触发器,触发器Trigger最基本的功能是指定Job的执行时间,执行间隔,运行次数等。
    这里写图片描述

    (3)有了Job和Trigger后,怎么样将两者结合起来呢?即怎样指定Trigger去执行指定的Job呢?这时需要一个Schedule,来负责这个功能的实现。
    这里写图片描述

    上面三个部分就是Quartz的基本组成部分:

    • 调度器:Scheduler
    • 任务:JobDetail
    • 触发器:Trigger,包括SimpleTrigger和CronTrigger

    二、Quartz Demo搭建

    下面来利用Quartz搭建一个最基本的Demo。
    1、导入依赖的jar包:

    <!-- https://mvnrepository.com/artifact/org.quartz-scheduler/quartz -->
    <dependency>
        <groupId>org.quartz-scheduler</groupId>
        <artifactId>quartz</artifactId>
        <version>2.3.0</version>
    </dependency>

    2、新建一个能够打印任意内容的Job:

    /**
     * Created by wanggenshen
     * Date: on 2018/7/7 16:28.
     * Description: 打印任意内容
     */
    public class PrintWordsJob implements Job{
    
        @Override
        public void execute(JobExecutionContext jobExecutionContext) throws JobExecutionException {
            String printTime = new SimpleDateFormat("yy-MM-dd HH-mm-ss").format(new Date());
            System.out.println("PrintWordsJob start at:" + printTime + ", prints: Hello Job-" + new Random().nextInt(100));
    
        }
    }
    
    

    3、创建Schedule,执行任务:

    /**
     * Created by wanggenshen
     * Date: on 2018/7/7 16:31.
     * Description: XXX
     */
    public class MyScheduler {
    
        public static void main(String[] args) throws SchedulerException, InterruptedException {
            // 1、创建调度器Scheduler
            SchedulerFactory schedulerFactory = new StdSchedulerFactory();
            Scheduler scheduler = schedulerFactory.getScheduler();
            // 2、创建JobDetail实例,并与PrintWordsJob类绑定(Job执行内容)
            JobDetail jobDetail = JobBuilder.newJob(PrintWordsJob.class)
                                            .withIdentity("job1", "group1").build();
            // 3、构建Trigger实例,每隔1s执行一次
            Trigger trigger = TriggerBuilder.newTrigger().withIdentity("trigger1", "triggerGroup1")
                    .startNow()//立即生效
                    .withSchedule(SimpleScheduleBuilder.simpleSchedule()
                    .withIntervalInSeconds(1)//每隔1s执行一次
                    .repeatForever()).build();//一直执行
    
            //4、执行
            scheduler.scheduleJob(jobDetail, trigger);
            System.out.println("--------scheduler start ! ------------");
            scheduler.start();
    
            //睡眠
            TimeUnit.MINUTES.sleep(1);
            scheduler.shutdown();
            System.out.println("--------scheduler shutdown ! ------------");
    
    
        }
    }
    
    

    运行程序,可以看到程序每隔1s会打印出内容,且在一分钟后结束:
    这里写图片描述

    三、Quartz核心详解

    下面就程序中出现的几个参数,看一下Quartz框架中的几个重要参数:

    • Job和JobDetail
    • JobExecutionContext
    • JobDataMap
    • Trigger、SimpleTrigger、CronTrigger

    (1)Job和JobDetail
    Job是Quartz中的一个接口,接口下只有execute方法,在这个方法中编写业务逻辑。
    接口中的源码:
    这里写图片描述

    JobDetail用来绑定Job,为Job实例提供许多属性:

    • name
    • group
    • jobClass
    • jobDataMap

    JobDetail绑定指定的Job,每次Scheduler调度执行一个Job的时候,首先会拿到对应的Job,然后创建该Job实例,再去执行Job中的execute()的内容,任务执行结束后,关联的Job对象实例会被释放,且会被JVM GC清除。

    为什么设计成JobDetail + Job,不直接使用Job

    JobDetail定义的是任务数据,而真正的执行逻辑是在Job中。
    这是因为任务是有可能并发执行,如果Scheduler直接使用Job,就会存在对同一个Job实例并发访问的问题。而JobDetail & Job 方式,Sheduler每次执行,都会根据JobDetail创建一个新的Job实例,这样就可以规避并发访问的问题。

    (2)JobExecutionContext
    JobExecutionContext中包含了Quartz运行时的环境以及Job本身的详细数据信息。
    当Schedule调度执行一个Job的时候,就会将JobExecutionContext传递给该Job的execute()中,Job就可以通过JobExecutionContext对象获取信息。
    主要信息有:
    这里写图片描述

    (3)JobExecutionContext
    JobDataMap实现了JDK的Map接口,可以以Key-Value的形式存储数据。
    JobDetail、Trigger都可以使用JobDataMap来设置一些参数或信息,
    Job执行execute()方法的时候,JobExecutionContext可以获取到JobExecutionContext中的信息:
    如:

    JobDetail jobDetail = JobBuilder.newJob(PrintWordsJob.class)                        .usingJobData("jobDetail1", "这个Job用来测试的")
                      .withIdentity("job1", "group1").build();
    
     Trigger trigger = TriggerBuilder.newTrigger().withIdentity("trigger1", "triggerGroup1")
          .usingJobData("trigger1", "这是jobDetail1的trigger")
          .startNow()//立即生效
          .withSchedule(SimpleScheduleBuilder.simpleSchedule()
          .withIntervalInSeconds(1)//每隔1s执行一次
          .repeatForever()).build();//一直执行
    

    Job执行的时候,可以获取到这些参数信息:

        @Override
        public void execute(JobExecutionContext jobExecutionContext) throws JobExecutionException {
    
            System.out.println(jobExecutionContext.getJobDetail().getJobDataMap().get("jobDetail1"));
            System.out.println(jobExecutionContext.getTrigger().getJobDataMap().get("trigger1"));
            String printTime = new SimpleDateFormat("yy-MM-dd HH-mm-ss").format(new Date());
            System.out.println("PrintWordsJob start at:" + printTime + ", prints: Hello Job-" + new Random().nextInt(100));
    
    
        }

    (4)Trigger、SimpleTrigger、CronTrigger

    • Trigger

    Trigger是Quartz的触发器,会去通知Scheduler何时去执行对应Job。

    new Trigger().startAt():表示触发器首次被触发的时间;
    new Trigger().endAt():表示触发器结束触发的时间;
    • SimpleTrigger
      SimpleTrigger可以实现在一个指定时间段内执行一次作业任务或一个时间段内多次执行作业任务。
      下面的程序就实现了程序运行5s后开始执行Job,执行Job 5s后结束执行:
    Date startDate = new Date();
    startDate.setTime(startDate.getTime() + 5000);
    
     Date endDate = new Date();
     endDate.setTime(startDate.getTime() + 5000);
    
            Trigger trigger = TriggerBuilder.newTrigger().withIdentity("trigger1", "triggerGroup1")
                    .usingJobData("trigger1", "这是jobDetail1的trigger")
                    .startNow()//立即生效
                    .startAt(startDate)
                    .endAt(endDate)
                    .withSchedule(SimpleScheduleBuilder.simpleSchedule()
                    .withIntervalInSeconds(1)//每隔1s执行一次
                    .repeatForever()).build();//一直执行
    
    • CronTrigger

    CronTrigger功能非常强大,是基于日历的作业调度,而SimpleTrigger是精准指定间隔,所以相比SimpleTrigger,CroTrigger更加常用。CroTrigger是基于Cron表达式的,先了解下Cron表达式:
    由7个子表达式组成字符串的,格式如下:

    [秒] [分] [小时] [日] [月] [周] [年]

    Cron表达式的语法比较复杂,
    如:* 30 10 ? * 1/5 *
    表示(从后往前看)
    [指定年份] 的[ 周一到周五][指定月][不指定日][上午10时][30分][指定秒]

    又如:00 00 00 ? * 10,11,12 1#5 2018
    表示2018年10、11、12月的第一周的星期五这一天的0时0分0秒去执行任务。

    下面是给的一个例子:
    这里写图片描述

    可通过在线生成Cron表达式的工具:http://cron.qqe2.com/ 来生成自己想要的表达式。
    这里写图片描述

    下面的代码就实现了每周一到周五上午10:30执行定时任务

    /**
     * Created by wanggenshen
     * Date: on 2018/7/7 20:06.
     * Description: XXX
     */
    public class MyScheduler2 {
        public static void main(String[] args) throws SchedulerException, InterruptedException {
            // 1、创建调度器Scheduler
            SchedulerFactory schedulerFactory = new StdSchedulerFactory();
            Scheduler scheduler = schedulerFactory.getScheduler();
            // 2、创建JobDetail实例,并与PrintWordsJob类绑定(Job执行内容)
            JobDetail jobDetail = JobBuilder.newJob(PrintWordsJob.class)
                    .usingJobData("jobDetail1", "这个Job用来测试的")
                    .withIdentity("job1", "group1").build();
            // 3、构建Trigger实例,每隔1s执行一次
            Date startDate = new Date();
            startDate.setTime(startDate.getTime() + 5000);
    
            Date endDate = new Date();
            endDate.setTime(startDate.getTime() + 5000);
    
            CronTrigger cronTrigger = TriggerBuilder.newTrigger().withIdentity("trigger1", "triggerGroup1")
                    .usingJobData("trigger1", "这是jobDetail1的trigger")
                    .startNow()//立即生效
                    .startAt(startDate)
                    .endAt(endDate)
                    .withSchedule(CronScheduleBuilder.cronSchedule("* 30 10 ? * 1/5 2018"))
                    .build();
    
            //4、执行
            scheduler.scheduleJob(jobDetail, cronTrigger);
            System.out.println("--------scheduler start ! ------------");
            scheduler.start();
            System.out.println("--------scheduler shutdown ! ------------");
    
        }
    }
    

    2018/07/07 20:10 in SH.

    展开全文
  • java定时任务

    2019-05-30 15:25:04
    在java中一个完整定时任务需要由Timer、TimerTask两个类来配合完成。 API中是这样定义他们的,Timer:一种工具,线程用其安排以后在后台线程中执行的任务。可安排任务执行一次,或者定期重复执行。由TimerTask:...

    转自:https://blog.csdn.net/strivenoend/article/details/80640031
    一、简介
    在java中一个完整定时任务需要由Timer、TimerTask两个类来配合完成。 API中是这样定义他们的,Timer:一种工具,线程用其安排以后在后台线程中执行的任务。可安排任务执行一次,或者定期重复执行。由TimerTask:Timer 安排为一次执行或重复执行的任务。我们可以这样理解Timer是一种定时器工具,用来在一个后台线程计划执行指定任务,而TimerTask一个抽象类,它的子类代表一个可以被Timer计划的任务。

    Timer类
    在工具类Timer中,提供了四个构造方法,每个构造方法都启动了计时器线程,同时Timer类可以保证多个线程可以共享单个Timer对象而无需进行外部同步,所以Timer类是线程安全的。但是由于每一个Timer对象对应的是单个后台线程,用于顺序执行所有的计时器任务,一般情况下我们的线程任务执行所消耗的时间应该非常短,但是由于特殊情况导致某个定时器任务执行的时间太长,那么他就会“独占”计时器的任务执行线程,其后的所有线程都必须等待它执行完,这就会延迟后续任务的执行,使这些任务堆积在一起,具体情况我们后面分析。

      当程序初始化完成Timer后,定时任务就会按照我们设定的时间去执行,Timer提供了schedule方法,该方法有多中重载方式来适应不同的情况,如下:
    
      schedule(TimerTask task, Date time):安排在指定的时间执行指定的任务。
    
      schedule(TimerTask task, Date firstTime, long period) :安排指定的任务在指定的时间开始进行重复的固定延迟执行。
    
      schedule(TimerTask task, long delay) :安排在指定延迟后执行指定的任务。
    
      schedule(TimerTask task, long delay, long period) :安排指定的任务从指定的延迟后开始进行重复的固定延迟执行。
    
      同时也重载了scheduleAtFixedRate方法,scheduleAtFixedRate方法与schedule相同,只不过他们的侧重点不同,区别后面分析。
    
      scheduleAtFixedRate(TimerTask task, Date firstTime, long period):安排指定的任务在指定的时间开始进行重复的固定速率执行。
    
      scheduleAtFixedRate(TimerTask task, long delay, long period):安排指定的任务在指定的延迟后开始进行重复的固定速率执行。
    

    TimerTask
    TimerTask类是一个抽象类,由Timer 安排为一次执行或重复执行的任务。它有一个抽象方法run()方法,该方法用于执行相应计时器任务要执行的操作。因此每一个具体的任务类都必须继承TimerTask,然后重写run()方法。

      另外它还有两个非抽象的方法:
    
      boolean cancel():取消此计时器任务。
    
      long scheduledExecutionTime():返回此任务最近实际执行的安排执行时间。
    

    二、实例
    2.1、指定延迟时间执行定时任务
    [java] view plain copy
    public class TimerTest01 {
    Timer timer;
    public TimerTest01(int time){
    timer = new Timer();
    timer.schedule(new TimerTaskTest01(), time * 1000);
    }

    public static void main(String[] args) {  
        System.out.println("timer begin....");  
        new TimerTest01(3);  
    }  
    

    }

    public class TimerTaskTest01 extends TimerTask{

    public void run() {  
        System.out.println("Time's up!!!!");  
    }  
    

    }
    public class TimerTest01 {
    Timer timer;
    public TimerTest01(int time){
    timer = new Timer();
    timer.schedule(new TimerTaskTest01(), time * 1000);
    }

    public static void main(String[] args) {
        System.out.println("timer begin....");
        new TimerTest01(3);
    }
    

    }

    public class TimerTaskTest01 extends TimerTask{

    public void run() {
        System.out.println("Time's up!!!!");
    }
    

    }

      运行结果:
    

    [java] view plain copy
    首先打印:timer begin…

    3秒后打印:Time’s up!!!
    首先打印:timer begin…

    3秒后打印:Time’s up!!!

      2.2、在指定时间执行定时任务
    

    [java] view plain copy
    public class TimerTest02 {
    Timer timer;

    public TimerTest02(){  
        Date time = getTime();  
        System.out.println("指定时间time=" + time);  
        timer = new Timer();  
        timer.schedule(new TimerTaskTest02(), time);  
    }  
      
    public Date getTime(){  
        Calendar calendar = Calendar.getInstance();  
        calendar.set(Calendar.HOUR_OF_DAY, 11);  
        calendar.set(Calendar.MINUTE, 39);  
        calendar.set(Calendar.SECOND, 00);  
        Date time = calendar.getTime();  
          
        return time;  
    }  
      
    public static void main(String[] args) {  
        new TimerTest02();  
    }  
    

    }

    public class TimerTaskTest02 extends TimerTask{

    @Override  
    public void run() {  
        System.out.println("指定时间执行线程任务...");  
    }  
    

    }
    public class TimerTest02 {
    Timer timer;

    public TimerTest02(){
        Date time = getTime();
        System.out.println("指定时间time=" + time);
        timer = new Timer();
        timer.schedule(new TimerTaskTest02(), time);
    }
    
    public Date getTime(){
        Calendar calendar = Calendar.getInstance();
        calendar.set(Calendar.HOUR_OF_DAY, 11);
        calendar.set(Calendar.MINUTE, 39);
        calendar.set(Calendar.SECOND, 00);
        Date time = calendar.getTime();
        
        return time;
    }
    
    public static void main(String[] args) {
        new TimerTest02();
    }
    

    }

    public class TimerTaskTest02 extends TimerTask{

    @Override
    public void run() {
        System.out.println("指定时间执行线程任务...");
    }
    

    }

      当时间到达11:39:00时就会执行该线程任务,当然大于该时间也会执行!!执行结果为:
    

    [java] view plain copy
    指定时间time=Tue Jun 10 11:39:00 CST 2014
    指定时间执行线程任务…
    指定时间time=Tue Jun 10 11:39:00 CST 2014
    指定时间执行线程任务…
    2.3、在延迟指定时间后以指定的间隔时间循环执行定时任务
    [java] view plain copy
    public class TimerTest03 {
    Timer timer;

    public TimerTest03(){  
        timer = new Timer();  
        timer.schedule(new TimerTaskTest03(), 1000, 2000);  
    }  
      
    public static void main(String[] args) {  
        new TimerTest03();  
    }  
    

    }

    public class TimerTaskTest03 extends TimerTask{

    @Override  
    public void run() {  
        Date date = new Date(this.scheduledExecutionTime());  
        System.out.println("本次执行该线程的时间为:" + date);  
    }  
    

    }
    public class TimerTest03 {
    Timer timer;

    public TimerTest03(){
        timer = new Timer();
        timer.schedule(new TimerTaskTest03(), 1000, 2000);
    }
    
    public static void main(String[] args) {
        new TimerTest03();
    }
    

    }

    public class TimerTaskTest03 extends TimerTask{

    @Override
    public void run() {
        Date date = new Date(this.scheduledExecutionTime());
        System.out.println("本次执行该线程的时间为:" + date);
    }
    

    }

      运行结果:
    

    [java] view plain copy
    本次执行该线程的时间为:Tue Jun 10 21:19:47 CST 2014
    本次执行该线程的时间为:Tue Jun 10 21:19:49 CST 2014
    本次执行该线程的时间为:Tue Jun 10 21:19:51 CST 2014
    本次执行该线程的时间为:Tue Jun 10 21:19:53 CST 2014
    本次执行该线程的时间为:Tue Jun 10 21:19:55 CST 2014
    本次执行该线程的时间为:Tue Jun 10 21:19:57 CST 2014

    本次执行该线程的时间为:Tue Jun 10 21:19:47 CST 2014
    本次执行该线程的时间为:Tue Jun 10 21:19:49 CST 2014
    本次执行该线程的时间为:Tue Jun 10 21:19:51 CST 2014
    本次执行该线程的时间为:Tue Jun 10 21:19:53 CST 2014
    本次执行该线程的时间为:Tue Jun 10 21:19:55 CST 2014
    本次执行该线程的时间为:Tue Jun 10 21:19:57 CST 2014

    对于这个线程任务,如果我们不将该任务停止,他会一直运行下去。

      对于上面三个实例,LZ只是简单的演示了一下,同时也没有讲解scheduleAtFixedRate方法的例子,其实该方法与schedule方法一样!
    
      2.4、分析schedule和scheduleAtFixedRate
      1、schedule(TimerTask task, Date time)、schedule(TimerTask task, long delay)
    
      对于这两个方法而言,如果指定的计划执行时间scheduledExecutionTime<= systemCurrentTime,则task会被立即执行。scheduledExecutionTime不会因为某一个task的过度执行而改变。
    
      2、schedule(TimerTask task, Date firstTime, long period)、schedule(TimerTask task, long delay, long period)
    
      这两个方法与上面两个就有点儿不同的,前面提过Timer的计时器任务会因为前一个任务执行时间较长而延时。在这两个方法中,每一次执行的task的计划时间会随着前一个task的实际时间而发生改变,也就是scheduledExecutionTime(n+1)=realExecutionTime(n)+periodTime。也就是说如果第n个task由于某种情况导致这次的执行时间过程,最后导致systemCurrentTime>= scheduledExecutionTime(n+1),这是第n+1个task并不会因为到时了而执行,他会等待第n个task执行完之后再执行,那么这样势必会导致n+2个的执行实现scheduledExecutionTime放生改变即scheduledExecutionTime(n+2) = realExecutionTime(n+1)+periodTime。所以这两个方法更加注重保存间隔时间的稳定。
    
      3、scheduleAtFixedRate(TimerTask task, Date firstTime, long period)、scheduleAtFixedRate(TimerTask task, long delay, long period)
    
      在前面也提过scheduleAtFixedRate与schedule方法的侧重点不同,schedule方法侧重保存间隔时间的稳定,而scheduleAtFixedRate方法更加侧重于保持执行频率的稳定。为什么这么说,原因如下。在schedule方法中会因为前一个任务的延迟而导致其后面的定时任务延时,而scheduleAtFixedRate方法则不会,如果第n个task执行时间过长导致systemCurrentTime>= scheduledExecutionTime(n+1),则不会做任何等待他会立即执行第n+1个task,所以scheduleAtFixedRate方法执行时间的计算方法不同于schedule,而是scheduledExecutionTime(n)=firstExecuteTime +n*periodTime,该计算方法永远保持不变。所以scheduleAtFixedRate更加侧重于保持执行频率的稳定。
    

    三、Timer的缺陷
    3.1、Timer的缺陷
    Timer计时器可以定时(指定时间执行任务)、延迟(延迟5秒执行任务)、周期性地执行任务(每隔个1秒执行任务),但是,Timer存在一些缺陷。首先Timer对调度的支持是基于绝对时间的,而不是相对时间,所以它对系统时间的改变非常敏感。其次Timer线程是不会捕获异常的,如果TimerTask抛出的了未检查异常则会导致Timer线程终止,同时Timer也不会重新恢复线程的执行,他会错误的认为整个Timer线程都会取消。同时,已经被安排单尚未执行的TimerTask也不会再执行了,新的任务也不能被调度。故如果TimerTask抛出未检查的异常,Timer将会产生无法预料的行为。

      1、Timer管理时间延迟缺陷
    
      前面Timer在执行定时任务时只会创建一个线程任务,如果存在多个线程,若其中某个线程因为某种原因而导致线程任务执行时间过长,超过了两个任务的间隔时间,会发生一些缺陷:
    

    [java] view plain copy
    public class TimerTest04 {
    private Timer timer;
    public long start;

    public TimerTest04(){  
        this.timer = new Timer();  
        start = System.currentTimeMillis();  
    }  
      
    public void timerOne(){  
        timer.schedule(new TimerTask() {  
            public void run() {  
                System.out.println("timerOne invoked ,the time:" + (System.currentTimeMillis() - start));  
                try {  
                    Thread.sleep(4000);    //线程休眠3000  
                } catch (InterruptedException e) {  
                    e.printStackTrace();  
                }  
            }  
        }, 1000);  
    }  
      
    public void timerTwo(){  
        timer.schedule(new TimerTask() {  
            public void run() {  
                System.out.println("timerOne invoked ,the time:" + (System.currentTimeMillis() - start));  
            }  
        }, 3000);  
    }  
      
    public static void main(String[] args) throws Exception {  
        TimerTest04 test = new TimerTest04();  
          
        test.timerOne();  
        test.timerTwo();  
    }  
    

    }
    public class TimerTest04 {
    private Timer timer;
    public long start;

    public TimerTest04(){
        this.timer = new Timer();
        start = System.currentTimeMillis();
    }
    
    public void timerOne(){
        timer.schedule(new TimerTask() {
            public void run() {
                System.out.println("timerOne invoked ,the time:" + (System.currentTimeMillis() - start));
                try {
                    Thread.sleep(4000);    //线程休眠3000
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        }, 1000);
    }
    
    public void timerTwo(){
        timer.schedule(new TimerTask() {
            public void run() {
                System.out.println("timerOne invoked ,the time:" + (System.currentTimeMillis() - start));
            }
        }, 3000);
    }
    
    public static void main(String[] args) throws Exception {
        TimerTest04 test = new TimerTest04();
        
        test.timerOne();
        test.timerTwo();
    }
    

    }
    按照我们正常思路,timerTwo应该是在3s后执行,其结果应该是:

    [java] view plain copy
    timerOne invoked ,the time:1001
    timerOne invoked ,the time:3001
    timerOne invoked ,the time:1001
    timerOne invoked ,the time:3001
    但是事与愿违,timerOne由于sleep(4000),休眠了4S,同时Timer内部是一个线程,导致timeOne所需的时间超过了间隔时间,结果:

    [java] view plain copy
    timerOne invoked ,the time:1000
    timerOne invoked ,the time:5000
    timerOne invoked ,the time:1000
    timerOne invoked ,the time:5000
    2、Timer抛出异常缺陷

      如果TimerTask抛出RuntimeException,Timer会终止所有任务的运行。如下:
    

    [html] view plain copy
    public class TimerTest04 {
    private Timer timer;

    public TimerTest04(){  
        this.timer = new Timer();  
    }  
      
    public void timerOne(){  
        timer.schedule(new TimerTask() {  
            public void run() {  
                throw new RuntimeException();  
            }  
        }, 1000);  
    }  
      
    public void timerTwo(){  
        timer.schedule(new TimerTask() {  
              
            public void run() {  
                System.out.println("我会不会执行呢??");  
            }  
        }, 1000);  
    }  
      
    public static void main(String[] args) {  
        TimerTest04 test = new TimerTest04();  
        test.timerOne();  
        test.timerTwo();  
    }  
    

    }
    public class TimerTest04 {
    private Timer timer;

    public TimerTest04(){
        this.timer = new Timer();
    }
    
    public void timerOne(){
        timer.schedule(new TimerTask() {
            public void run() {
                throw new RuntimeException();
            }
        }, 1000);
    }
    
    public void timerTwo(){
        timer.schedule(new TimerTask() {
            
            public void run() {
                System.out.println("我会不会执行呢??");
            }
        }, 1000);
    }
    
    public static void main(String[] args) {
        TimerTest04 test = new TimerTest04();
        test.timerOne();
        test.timerTwo();
    }
    

    }
    运行结果:timerOne抛出异常,导致timerTwo任务终止。

    [java] view plain copy
    Exception in thread “Timer-0” java.lang.RuntimeException
    at com.chenssy.timer.TimerTest04$1.run(TimerTest04.java:25)
    at java.util.TimerThread.mainLoop(Timer.java:555)
    at java.util.TimerThread.run(Timer.java:505)
    Exception in thread “Timer-0” java.lang.RuntimeException
    at com.chenssy.timer.TimerTest04$1.run(TimerTest04.java:25)
    at java.util.TimerThread.mainLoop(Timer.java:555)
    at java.util.TimerThread.run(Timer.java:505)
    对于Timer的缺陷,我们可以考虑 ScheduledThreadPoolExecutor 来替代。Timer是基于绝对时间的,对系统时间比较敏感,而ScheduledThreadPoolExecutor 则是基于相对时间;Timer是内部是单一线程,而ScheduledThreadPoolExecutor内部是个线程池,所以可以支持多个任务并发执行。

    3.2、用ScheduledExecutorService替代Timer
    1、解决问题一:

    [java] view plain copy
    public class ScheduledExecutorTest {
    private ScheduledExecutorService scheduExec;

    public long start;  
      
    ScheduledExecutorTest(){  
        this.scheduExec =  Executors.newScheduledThreadPool(2);    
        this.start = System.currentTimeMillis();  
    }  
      
    public void timerOne(){  
        scheduExec.schedule(new Runnable() {  
            public void run() {  
                System.out.println("timerOne,the time:" + (System.currentTimeMillis() - start));  
                try {  
                    Thread.sleep(4000);  
                } catch (InterruptedException e) {  
                    e.printStackTrace();  
                }  
            }  
        },1000,TimeUnit.MILLISECONDS);  
    }  
      
    public void timerTwo(){  
        scheduExec.schedule(new Runnable() {  
            public void run() {  
                System.out.println("timerTwo,the time:" + (System.currentTimeMillis() - start));  
            }  
        },2000,TimeUnit.MILLISECONDS);  
    }  
      
    public static void main(String[] args) {  
        ScheduledExecutorTest test = new ScheduledExecutorTest();  
        test.timerOne();  
        test.timerTwo();  
    }  
    

    }
    public class ScheduledExecutorTest {
    private ScheduledExecutorService scheduExec;

    public long start;
    
    ScheduledExecutorTest(){
        this.scheduExec =  Executors.newScheduledThreadPool(2);  
        this.start = System.currentTimeMillis();
    }
    
    public void timerOne(){
        scheduExec.schedule(new Runnable() {
            public void run() {
                System.out.println("timerOne,the time:" + (System.currentTimeMillis() - start));
                try {
                    Thread.sleep(4000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        },1000,TimeUnit.MILLISECONDS);
    }
    
    public void timerTwo(){
        scheduExec.schedule(new Runnable() {
            public void run() {
                System.out.println("timerTwo,the time:" + (System.currentTimeMillis() - start));
            }
        },2000,TimeUnit.MILLISECONDS);
    }
    
    public static void main(String[] args) {
        ScheduledExecutorTest test = new ScheduledExecutorTest();
        test.timerOne();
        test.timerTwo();
    }
    

    }

      运行结果:
    

    [java] view plain copy
    timerOne,the time:1003
    timerTwo,the time:2005
    timerOne,the time:1003
    timerTwo,the time:2005

      2、解决问题二
    

    [java] view plain copy
    public class ScheduledExecutorTest {
    private ScheduledExecutorService scheduExec;

    public long start;  
      
    ScheduledExecutorTest(){  
        this.scheduExec =  Executors.newScheduledThreadPool(2);    
        this.start = System.currentTimeMillis();  
    }  
      
    public void timerOne(){  
        scheduExec.schedule(new Runnable() {  
            public void run() {  
                throw new RuntimeException();  
            }  
        },1000,TimeUnit.MILLISECONDS);  
    }  
      
    public void timerTwo(){  
        scheduExec.scheduleAtFixedRate(new Runnable() {  
            public void run() {  
                System.out.println("timerTwo invoked .....");  
            }  
        },2000,500,TimeUnit.MILLISECONDS);  
    }  
      
    public static void main(String[] args) {  
        ScheduledExecutorTest test = new ScheduledExecutorTest();  
        test.timerOne();  
        test.timerTwo();  
    }  
    

    }
    public class ScheduledExecutorTest {
    private ScheduledExecutorService scheduExec;

    public long start;
    
    ScheduledExecutorTest(){
        this.scheduExec =  Executors.newScheduledThreadPool(2);  
        this.start = System.currentTimeMillis();
    }
    
    public void timerOne(){
        scheduExec.schedule(new Runnable() {
            public void run() {
                throw new RuntimeException();
            }
        },1000,TimeUnit.MILLISECONDS);
    }
    
    public void timerTwo(){
        scheduExec.scheduleAtFixedRate(new Runnable() {
            public void run() {
                System.out.println("timerTwo invoked .....");
            }
        },2000,500,TimeUnit.MILLISECONDS);
    }
    
    public static void main(String[] args) {
        ScheduledExecutorTest test = new ScheduledExecutorTest();
        test.timerOne();
        test.timerTwo();
    }
    

    }

     运行结果:
    

    [java] view plain copy
    timerTwo invoked …
    timerTwo invoked …
    timerTwo invoked …
    timerTwo invoked …
    timerTwo invoked …
    timerTwo invoked …
    timerTwo invoked …
    timerTwo invoked …
    timerTwo invoked …

    timerTwo invoked …
    timerTwo invoked …
    timerTwo invoked …
    timerTwo invoked …
    timerTwo invoked …
    timerTwo invoked …
    timerTwo invoked …
    timerTwo invoked …
    timerTwo invoked …

    展开全文
  • 定时任务

    2018-07-11 01:55:09
    基本格式 : * * * * * command 分 时 日 月 周 命令 第1列表示分钟1~59 每分钟用*或者 */1表示 第2列表示小时1~23(0表示0点) ...crontab -l //列出自己的所有cron任务 crontab -r //删除自己的...
  • 分布式定时任务

    2018-11-02 16:39:30
    什么是定时任务 定时任务是指基于给定的时间点、给定的时间间隔或者给定的执行次数自动执行一项或多项任务。 常用的定时任务如下。 1.crontab命令 直接使用linux系统自带的crontab程序进行定时任务控制。 2.JDK...
  • 定时任务的实现

    2018-11-20 09:27:51
     Java自带的java.util.Timer类,这个类允许你调度一个java.util.TimerTask任务。使用这种方式可以让你的程序按照某一个频度执行,但不能在指定时间运行。一般用的较少。  Quartz 这是一个功能比较强大的的调度器...
  • Linux学习之定时任务

    2020-04-26 22:02:16
    定时任务概念,以及常用Linux工具如at、crontab、anacron、Systemd
  • 大多数系统都会遇到关于定时任务相关的功能,例如定时备份数据库,定时修改某些信息,订单30分钟自动消单等,很多功能可以基于linux的定时命令去完成,有一些特殊的与业务系统紧密相关的任务则需要代码来完成,而...
  • 在jdk自带的库中,有两种技术可以实现定时任务。一种是使用Timer,另外一个则是ScheduledThreadPoolExecutor。下面为大家分析一下这两个技术的底层实现原理以及各自的优缺点。 一、Timer 1. Timer的使用 ...
  • java实现定时任务 Schedule https://blog.csdn.net/java_2017_csdn/article/details/78060204 2017年09月22日 10:30:52Java_2017_csdn阅读数:3306 java实现定时任务 Schedule 标签:java实现定时任务 Schedule...
  • win10定时任务

    2018-08-10 18:38:32
    用python实现邮件发送已经写过一篇不再重复,此篇补充定时任务。 由于本宝宝系统是win10,没装Linux,发邮件给甲方爸爸只是短暂任务,so并没有打算用Linux,win也是可以实现的。那我们就开始吧: 1、点"此...
  • SpringQuartz定时任务的使用,要配置这个定时任务什么时候执行,周期是多少,周期内执行多少次,这个都是cron表达式来控制的,下面详解一下这个cron表达式。 一、先来举些例子 【1】0 0 10,14,16 * * ? 每天上午10...
  • 现在的web项目中很多场景下都有要执行定时任务的需求,比如说每隔5秒将redis中的统计当天注册用户数持久化到数据库。现在项目中实现这一功能,一般用quartz这一工具框架,但是这个对于一个新手来说比较麻烦,各种查...
  • github:https://github.com/jiasion/eslog wechat:minghui-666 利用redisson实现多实例抢占定时任务 pom.xml <dependency> <groupId>org.redisson</groupId> <artifactId>r...
  • 创建定时任务实体类 任务实体持久化 创建调度器工厂 TaskUtils工具类 创建定时任务服务类 创建定时任务初始化服务类 创建定时任务实现类 在实际项目开发过程中,定时任务几乎是必不可少的。作为Java程序员用...
  • 详解java定时任务

    2014-06-20 12:08:27
    在我们编程过程中如果需要执行一些简单的定时任务,无须做复杂的控制,我们可以考虑使用JDK中的Timer定时任务来实现。下面LZ就其原理、实例以及Timer缺陷三个方面来解析java Timer定时器。一、简介 在java中一个...
  • 摘要: 在开发测试工具的应用后台,经常听到同事说要做个定时任务把做日志处理,或者数据清理,包括做些复杂的业务计算逻辑,在选择定时任务的时候,怎么能够快速实现,并且选择一种更适合自己的方式呢?我这里把...
  • JAVA实现定时任务的几种方式@(JAVA)[spring|quartz|定时器]  近期项目开发中需要动态的添加定时任务,比如在某个活动结束时,自动生成获奖名单,导出excel等,此类任务由于活动时间是动态的,不能把定时任务配置在...
  • 原文地址:SpringBoot几种定时任务的实现方式 定时任务实现的几种方式: Timer:这是java自带的java.util.Timer类,这个类允许你调度一个java.util.TimerTask任务。使用这种方式可以让你的程序按照某一个频度执行...
1 2 3 4 5 ... 20
收藏数 245,054
精华内容 98,021
关键字:

定时任务