Timer与Quartz--Android和Java开发你都需要了解

前言:关于任务定时调度,Android基本用的要么Timer要么Handler配合Timer,而Java后端基本也是Timer;而其实,除了Timer,还有一个更强大的任务调度工具----Quartz。

Timer【概述】


Timer就不多说了

  • 作用: 在一个子线程中执行定时或循环的任务。
  • 包:java.util.Timer
  • 相关方法:Timer.schedule(参数)
    参数如下:

举一个简单的例子:
问:现在有两个任务,即时任务A 【执行一次/ 1s】和耗时任务B【执行一次/ 500s】,我想先启动任务A,5s后启动任务B,并且10s后让两个任务都结束。
答:

Timer timer = new Timer();
timer.schedule(new TimerTask() {// 任务A
     @Override
     public void run() {
        System.out.println("Timer正在调度 线程:" + Thread.currentThread().getName() + " 执行任务 A 中。。。");
       }
}, 0, 1000)
timer.schedule(new TimerTask() {// 任务B
     @Override
     public void run() {
        System.out.println("Timer正在调度 线程:" + Thread.currentThread().getName() + " 执行任务 B 中。。。");
       try {
          System.out.println("任务B很慢,它让Timer单例线程睡眠3s");
              Thread.sleep(3000);
       } catch (InterruptedException e) {
           e.printStackTrace();
       }
    }
}, 5000, 500);
try {
    System.out.println("主线程睡眠10s");
    Thread.sleep(10000);
    timer.cancel();
    System.out.println("主线程关闭了Timer的所有任务");
} catch (InterruptedException e) {
    e.printStackTrace();
}

Quartz【重点】


Quartz,一句话: 他就是Timer 的升级版
github地址: https://github.com/quartz-scheduler/quartz

Quartz 总体架构

一、最主要的接口和类

  • 接口
    • Job:任务。
    • JobDetail: 任务详情(extends Serializable, Cloneable)。
    • Trigger: 触发器(extends Serializable, Cloneable, Comparable<Trigger>)。
    • SchedulerFactory:调度器工厂。
    • Scheduler: 调度器。
    • JobBuilder:任务详情构造器。
    • TriggerBuilder:触发器构造器。
    • StdSchedulerFactory:具体的调度器工厂。
    • SimpleTrigger:Trigger实现类,和Timer实现功能差不多。
    • CronTrigger:Trigger实现类,也是Quartz拉开Timer的地方。
    • StdSchedulerFactory:标准的调度器工厂类

二、例子

上个代码可能更容易懂:

  1. 导入Quartz 包:
  • Maven:
    <dependency>
            <groupId>org.quartz-scheduler</groupId>
            <artifactId>quartz</artifactId>
            <version>2.3.0</version>
    </dependency>
    
  • Gradle:
    compile 'org.quartz-scheduler:quartz:2.3.0'
    
  1. 首先,创建一个Job实现类,名为JobImpl
public class JobImpl implements Job{
    ///获取key:value 方法二:通过设置与key同名同属性的成员变量,并设置getter和setter方法来实现值获取
    // 在Quartz反射构建Job对象实例时,会自动判断和调用相应的setter方法,传入key同名的参数。
    private String stringkeyA;
    private Double doubleKeyB;
    public String getStringkeyA() {
        return stringkeyA;
    }

    public void setStringkeyA(String stringkeyA) {
        this.stringkeyA = stringkeyA;
    }

    public Double getDoubleKeyB() {
        return doubleKeyB;
    }

    public void setDoubleKeyB(Double doubleKeyB) {
        this.doubleKeyB = doubleKeyB;
    }

    public void execute(JobExecutionContext context) throws JobExecutionException {
        ///do sth

        ///Job 可以获取 的 信息如下:

        ///获取 JobDetail 对象引用
        JobDetail jobDetail = context.getJobDetail();

        ///获取job 的 标识名 和 所在组别
        JobKey jobKey = jobDetail.getKey();
        jobKey.getName();
        jobKey.getGroup();

        /// 获取 Trigger 对象引用
        Trigger trigger  = context.getTrigger();

        ///获取trigger 的 标识名 和 所在组别
        TriggerKey triggerKey = trigger.getKey();
        triggerKey.getName();
        triggerKey.getGroup();

        // 获取 JobDetail 中设定的key:value
        JobDataMap jobMap = jobDetail.getJobDataMap();
        // 获取 Trigger 中设定的key:value
        JobDataMap triggerMap = trigger.getJobDataMap();
        /// 合并并获取 JobDetail 和 Trigger 对象上设置的 key:value
        JobDataMap map  = context.getMergedJobDataMap();

        ///获取key:value 方法一:通过Map中获取
        String stringKeyA = map.getString("stringKeyA");
        String doubleKeyA = map.getString("doubleKeyA");
    }
}
  1. 然后,根据这个JobImpl和一些任务参数配置,构造一个专门做JobImpl这类任务的JobDetail对象:
//创建一个JobDetail ,并让其与 JobImpl 绑定起来
JobDetail jobDetail = JobBuilder
    .newJob(JobImpl.class)
    .withIdentity("JobA", "group1")
    .usingJobData("stringkeyA","任务DataA")
    .usingJobData("doublekeyB",2.15D)
    .build();
  1. 接着,创建一个Trigger对象,用于待会被调度器触发,Trigger用于设置任务的触发时间以及触发周期等。
//创建一个Trigger实例,并定义该job立即执行,每隔2s重复执行一次,直到永远
Trigger trigger = TriggerBuilder.newTrigger().withIdentity("triggerA", "group1")
    .startNow()
    .withSchedule(SimpleScheduleBuilder.simpleSchedule().withIntervalInSeconds(2).repeatForever())
    .usingJobData("stringkeyA","Trigger DataB")
    .usingJobData("floatkeyC",3.1f)
    .build();
  1. 最后,创建一个调度器,让其开始跑任务。
SchedulerFactory factory = new StdSchedulerFactory();///标准工厂
Scheduler scheduler = factory.getScheduler();///构建 调度器 实例
scheduler.scheduleJob(jobDetail,cronTrigger);// 让调度器 绑定 任务详情 和 触发器
scheduler.start();//执行任务
//........
scheduler.standby();//我想暂时挂起调度器,不让其执行任务
//........
scheduler.start();/// 重启调度器
//........
scheduler.shutdown(true);//终止调度器(true:等所有job都跑完,才终止;false:不等job,直接终止)

三、关于Trigger实现类

  1. SimpleTrigger
//以上的Trigger 实际上默认就是一个SimpleTrigger
Date startTime = new Date(System.currentTimeMillis());
Date endTime = new Date(System.currentTimeMillis()+3000);
SimpleTrigger simpleTrigger = (SimpleTrigger) TriggerBuilder.newTrigger()
      .withIdentity("mySimpleTrigger","G1")
      .startAt(startTime)
      .endAt(endTime)
      .withSchedule(SimpleScheduleBuilder
              .simpleSchedule()
              .withRepeatCount(3)//重复次数:0,正整数,或无穷
              .withIntervalInSeconds(2))// 执行周期: 0,长整数
      .build();
  1. CropTrigger【重点】
  • 基于Cron表达式:
    1. 由7个子表达式组成 的 字符串,描述了时间表的详细信息
    2. 格式: [秒] [分] [小时] [日] [月] [周] [年]
  • Cron表达式规则:


    • 符号意义:[, 或者] [- 范围] [* 每] [/ 每隔] [? 不关心(与*意义差不多)] [L 最后的一X] [W 距离X号最近的一个工作] [# 第]。
    • Cron表达式例子:
      1. 0 15 10 ? * *:每天10点15分触发。
      2. 0 0/5 14 * * ?:每天下午2点到2点59分,每隔5分钟执行一次。
      3. 0 15 10 ? * MON-FRI:周一到周五每天上午10点15分触发。
      4. 0 15 10 ? * 7#3:每月第三周的星期六 触发。
      5. 0 15 10 ? * 6L 2016-2017:从2016年到2017年每个月最后一周的周五触发。
  • CropTrigger例子:
    ///CronTrigger 基于日历的调度(功能强大, 可定义每周周几 执行一次任务 之类的)
    /**
     * Cron表达式:
     * 由7个子表达式组成 的 字符串,描述了时间表的详细信息
     * 格式: [秒] [分] [小] [时] [日] [月] [周] [年]
     */
    

CronTrigger cronTrigger = TriggerBuilder
.newTrigger()
.withIdentity("cronTrigger", "G1")
.withSchedule(
CronScheduleBuilder.cronSchedule("* * * * * ? *")///每一秒执行一次
)
.build();


### 四、注意点
1. **`ExecutionContext`在构建任务调度器的时候,就会被引入到`Job`中。**
2. **`ExecutionContext`本身拥有`JobDetail` 和`Trigger`的引用,这样,就可以给`Job`的执行过程中,随时获取到`JobDetail`和`Trigger`的信息。**
3. **`JobDataMap`是`ExecutionContext`携带的重要参数,它携带了`JobDetail`和`Trigger`在构造时设置的一些基本数据类型的参数。**
4. **`Job`获取`JobDataMap`中的参数的方式**【上述例子中有注释】
  * 法一:直接从Map对象中获取的方式
    ```
 JobDataMap jMap = jobExecutionContext.getJobDetail().getJobDataMap();
 JobDataMap tMap = jobExecutionContext.getTrigger().getJobDataMap();
 JobDataMap mergeMap = jobExecutionContext.getMergedJobDataMap();
    ```
    > 注意:上述`mergeMap`会将`Trigger`中设定的属性,覆盖掉`JobDetail`中设定的同名属性。
  * 法二:自定义Job子类中事先定义几个成员属性并实现其getter和setter方法,在构建Job实例时,会自动调用Job子类的setter方法,将key同名的value赋值到Job实例中。
5. **关于quartz.properties文件**
  * 它是调度器参数的配置文件,包括:
    1. 调度器属性
    2. 线程池属性
    3. 存储调度信息
    4. 插件属性配置
  * `StdSchedulerFactory`使用`Java.util.Properties`来创建和初始化Quartz调度器,在创建`Scheduler`对象时,就是根据`quartz.properties`文件中配置信息。
  * Quartz 默认情况下,会读取project下的`quartz.properties`文件,找不到文件,则会去查找引入的jar包下的`quartz.properties`文件,此文件拥有Quartz的一些相关配置信息。
  * 用法:
    1. 一般情况下,将`Jar包目录/org.quartz/`下的`quartz.properties`文件copy 到你的项目根目录下。
    2. 然后,可以自行配置各个参数。【具体配置可以查看[Quartz官网](http://www.quartz-scheduler.org/)】

# 总结一波
---
 实际上, Timer 和 Quartz 有一个共同的理念,就是:将想要在某些时刻执行的任务交给工作线程,并让该线程通过阻塞等机制,到了某一时刻执行。
 只不过,Timer内部是一个单例线程和一个数组类型的队列结构,多个任务可以入队等待,但同一时刻只有一个任务会被执行,并没有并发这一点,每个任务在执行一次后,如果有循环,那么则会重新入队。
  而Quartz ,相对于Timer的优势,总体来说,两点:
  1. 更强的定时调度能力:Quartz的CronTrigger,拥有Timer无法实现的日历定时调度功能。
  2. 任务并发能力:Quartz内部通过线程池管理多个线程,能够让任务并发执行,从而避免了使用Timer时由于前一个耗时任务导致队列中后一个任务错过执行时间的情况。
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 204,590评论 6 478
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 86,808评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 151,151评论 0 337
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,779评论 1 277
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,773评论 5 367
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,656评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,022评论 3 398
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,678评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 41,038评论 1 299
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,659评论 2 321
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,756评论 1 330
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,411评论 4 321
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,005评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,973评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,203评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,053评论 2 350
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,495评论 2 343

推荐阅读更多精彩内容