Java中定時任務(wù)調(diào)度的實(shí)現(xiàn)

在工作中遇到一個需求,需要定時自動執(zhí)行某項(xiàng)功能,這就需要用到定時任務(wù)了育叁。

定時任務(wù)調(diào)度:基于給定的時間點(diǎn)洼怔,給定的時間間隔或者給定的執(zhí)行次數(shù)自動執(zhí)行任務(wù)。

定時任務(wù)調(diào)度的幾種實(shí)現(xiàn)方式:

Timer:
Timer由JDK自帶,不需要引入多余的jar框都。Java自帶的java.util.Timer類,這個類允許你調(diào)度一個java.util.TimerTask任務(wù)呵晨。Timer只有一個后臺線程執(zhí)行任務(wù),使用這種方式可以讓你的程序按照某一個頻度執(zhí)行魏保,但不能在指定時間運(yùn)行。一般用的較少摸屠。
Quartz:
使用Quartz谓罗,這是一個功能比較強(qiáng)大的的調(diào)度器,可以讓你的程序在指定時間執(zhí)行季二,也可以按照某一個頻度執(zhí)行妥衣,配置起來稍顯復(fù)雜,Quartz擁有后臺執(zhí)行線程池能夠使用多個線程
Spring Task:
Spring3.0以后自帶的task戒傻,可以將它看成一個輕量級的Quartz税手,而且使用起來比Quartz簡單許多。

Timer代碼實(shí)現(xiàn)

首先寫個需要定時實(shí)現(xiàn)的邏輯,這里是簡單輸出格式化后的時間

public class MyTimerTask extends TimerTask{
    @Override
    public void run() {
        SimpleDateFormat df = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
        System.out.println(df.format(System.currentTimeMillis()));
    }
}

然后寫定時任務(wù)調(diào)度類

public class MyTimer {
    public static void main(String[] args){
        //創(chuàng)建timer實(shí)例
        Timer timer=new Timer();
        //創(chuàng)建一個MyTimerTask實(shí)例
        MyTimerTask myTimerTask=new MyTimerTask();
        //通過timer定時調(diào)用myTimerTask中的run方法,因?yàn)?TimerTask 類實(shí)現(xiàn)了 Runnable 接口需纳。
        timer.schedule(myTimerTask,10*1000,2*1000);
    }
}
QQ截圖20180609173715.png

這里的重點(diǎn)是Timer中schedule()方法的三個參數(shù)
第一個參數(shù):是 就是我們剛剛寫的MyTimerTask類芦倒,這里需要繼承TimerTask,并實(shí)現(xiàn) run() 方法不翩,因?yàn)?TimerTask 類實(shí)現(xiàn)了 Runnable 接口兵扬。
第二個參數(shù):是程序啟動后,timer定時器第一次調(diào)用run方法的時間口蝠,0表示不指時間器钟,立刻調(diào)用。(10*1000表示10秒妙蔗,因?yàn)閰?shù)單位是毫秒)
第三個參數(shù):是指第一次調(diào)用之后傲霸,從第二次開始每隔多長的時間調(diào)用一次 run() 方法。單位同樣是毫秒眉反。

拓展:

QQ圖片20180609191051.png

其實(shí)Timer中schedule()方法一共有四種用法昙啄,剛剛只是其中一種
schedule(task, time):time為Date類型,在指定時間執(zhí)行一次寸五。
schedule(task, firstTime, period):firstTime為Date類型梳凛,從firstTime時刻開始,每隔period毫秒執(zhí)行一次梳杏。
schedule(task, delay):從現(xiàn)在起過delay毫秒執(zhí)行一次

QQ截圖20180609194229.png

scheduleAtFixedRate(task,delay,period)schedule(task,delay,period)基本一樣
scheduleAtFixedRate(task,firstTime,period)schedule(task,firstTime,period)基本一樣

區(qū)別可以參考:https://blog.csdn.net/gtuu0123/article/details/6040159

測試結(jié)果:

QQ截圖20180609174352.png

每隔兩秒輸出一次時間韧拒,第二個參數(shù)在截圖上體現(xiàn)不了淹接,需要自己敲出來感受。
注:時效性要求較高的多任務(wù)并發(fā)作業(yè)叛溢,復(fù)雜的任務(wù)的調(diào)度不推薦使用

Spring Task

@Configuration
@EnableScheduling
public class SchedulingConfig {
    @Scheduled(cron = "0/5 * * * * ?") // 每5秒執(zhí)行一次
    public void Scheduled(){
        System.out.println("每5秒在控制臺打印一次"+new Date());
    }
}

結(jié)果如下

Paste_Image.png

spring task 在計(jì)算時間的時候蹈集,是根據(jù)當(dāng)前服務(wù)器的系統(tǒng)時間進(jìn)行計(jì)算.
如果是每10秒執(zhí)行一次的話,那么它是從系統(tǒng)時間的0,10,20秒進(jìn)行計(jì)算的.
如果是每1分鐘執(zhí)行一次的話雇初,那么它是從系統(tǒng)時間的1分鐘拢肆,2分鐘進(jìn)行計(jì)算的

如何動態(tài)修改cron參數(shù)
代碼解釋 一開始設(shè)定的是每20秒執(zhí)行一次,只要訪問下
http://localhost:8080/changeExpression
就可以改成每10秒執(zhí)行一次了

@RestController
@EnableScheduling
public class ConfigTask implements SchedulingConfigurer{
    private String expression="0/20 * * * * *";
    @RequestMapping("/changeExpression")
    public String changeExpression(){
        expression="0/10 * * * * *";
        return "changeExpression";
    }
@Override
    public void configureTasks(ScheduledTaskRegistrar scheduledTaskRegistrar) {
        Runnable task=new Runnable() {
            @Override
            public void run() {
                System.out.println("TaskCronChange task is running ... "+ new Date());
            }
        };
        Trigger trigger=new Trigger() {
            @Override
            public Date nextExecutionTime(TriggerContext triggerContext) {
                CronTrigger cronTrigger=new CronTrigger(expression);
                return cronTrigger.nextExecutionTime(triggerContext);
            }
        };
        scheduledTaskRegistrar.addTriggerTask(task,trigger);
    }
}

.nextExecutionTim()方法是為了返回一個date類型的數(shù)據(jù)
看起來很多代碼靖诗,其實(shí)就是先實(shí)現(xiàn)SchedulingConfigurer接口郭怪。
實(shí)現(xiàn)里面的方法,縮小成就這樣

public void configureTasks(ScheduledTaskRegistrar scheduledTaskRegistrar) {
        Runnable task=new Runnable() {...};
        Trigger trigger=new Trigger() {...};
        scheduledTaskRegistrar.addTriggerTask(task,trigger);
    }

項(xiàng)目需求:
如果有個這樣的場景刊橘,在某一段時間內(nèi)要執(zhí)行特定的操作

@RestController
@EnableScheduling
public class ConfigTask implements SchedulingConfigurer{
    private String expression="0/20 * * * * *";
    @RequestMapping("/changeExpression")
    public String changeExpression(){
        expression="0/10 * * * * *";
        return "changeExpression";
    }
    public void println(long start,long end){
        Date date=new Date();
        long time=date.getTime()/1000;
        if(start<time&&time<end){
            System.out.println("TaskCronChange task is running ... "+ new Date());
        }else {
            System.out.println("TaskCronChange task is stopping ... "+ new Date());
        }
    }
    @Override
    public void configureTasks(ScheduledTaskRegistrar scheduledTaskRegistrar) {
        Runnable task=new Runnable() {
            @Override
            public void run() {
                println(1494306514,1494306634);
            }
        };
        Trigger trigger=new Trigger() {
            @Override
            public Date nextExecutionTime(TriggerContext triggerContext) {
                CronTrigger cronTrigger=new CronTrigger(expression);
                return cronTrigger.nextExecutionTime(triggerContext);
            }
        };
        scheduledTaskRegistrar.addTriggerTask(task,trigger);
    }
}

把時間轉(zhuǎn)化成時間戳鄙才,再去對比時間
結(jié)果如下


Paste_Image.png

動態(tài)添加修改刪除定時任務(wù)
1.首先需要重新認(rèn)識一個類ThreadPoolTaskScheduler:線程池任務(wù)調(diào)度類,能夠開啟線程池進(jìn)行任務(wù)調(diào)度促绵。
2.ThreadPoolTaskScheduler.schedule()方法會創(chuàng)建一個定時計(jì)劃ScheduledFuture攒庵,在這個方法需要添加兩個參數(shù),Runnable(線程接口類) 和CronTrigger(定時任務(wù)觸發(fā)器)
3.在ScheduledFuture中有一個cancel可以停止定時任務(wù)败晴。

@RestController
@EnableScheduling
public class DynamicTask {
    @Autowired
    private ThreadPoolTaskScheduler threadPoolTaskScheduler;
    private ScheduledFuture<?> future;
    @Bean
    public ThreadPoolTaskScheduler threadPoolTaskScheduler() {
        return new ThreadPoolTaskScheduler();
    }

    @RequestMapping("/startCron")
    public String startCron() {
        future = threadPoolTaskScheduler.schedule(new MyRunnable(), new CronTrigger("0/5 * * * * *"));
        System.out.println("DynamicTask.startCron()");
        return "startCron";
    }

    @RequestMapping("/stopCron")
    public String stopCron() {
        if (future != null) {
            future.cancel(true);
        }
        System.out.println("DynamicTask.stopCron()");
        return "stopCron";
    }

    @RequestMapping("/changeCron10")
    public String startCron10() {
        stopCron();// 先停止浓冒,在開啟.
        future = threadPoolTaskScheduler.schedule(new MyRunnable(), new CronTrigger("*//**//*10 * * * * *"));
        System.out.println("DynamicTask.startCron10()");
        return "changeCron10";
    }

    private class MyRunnable implements Runnable {
        @Override
        public void run() {
            System.out.println("DynamicTask.MyRunnable.run()," + new Date());
        }
    }
}

在私有類MyRunnable中的run方法中可以寫入具體的定時任務(wù)邏輯
(a)我們首先了一個類DynamicTask;
(b)定義了兩個變量尖坤,threadPoolTaskSchedulerfuture 其中futuretreadPoolTaskScheduler執(zhí)行方法schedule的返回值稳懒,主要用于定時任務(wù)的停止。
(c)編寫啟動定時器的方法startCron()慢味;
(d)編寫停止方法stopCron(),這里編碼的時候场梆,需要注意下需要判斷下futurenull的時候,不然就很容易拋出NullPointerException纯路;
(e)編寫修改定時任務(wù)執(zhí)行周期方法changeCron10()或油,這里的原理就是關(guān)閉之前的定時器,創(chuàng)新在創(chuàng)建一個新的定時器驰唬。

Quartz

Quartz是OpenSymphony開源組織在Job scheduling領(lǐng)域又一個開源項(xiàng)目顶岸,是一個完全由Java編寫的開源作業(yè)調(diào)度框架。
官網(wǎng)地址:http://www.quartz-scheduler.org/
為在Java應(yīng)用程序中進(jìn)行作業(yè)調(diào)度提供了簡單卻強(qiáng)大的機(jī)制定嗓。Quartz允許開發(fā)人員根據(jù)時間間隔來調(diào)度作業(yè)蜕琴。它實(shí)現(xiàn)了作業(yè)和觸發(fā)器的多對多的關(guān)系,還能把多個作業(yè)與不同的觸發(fā)器關(guān)聯(lián)宵溅。

Quartz的特點(diǎn)

① 強(qiáng)大的調(diào)度功能,spring默認(rèn)的調(diào)度框架上炎, Quartz 很容易與 Spring 集成實(shí)現(xiàn)靈活可配置的調(diào)度功能恃逻,及時系統(tǒng)因故障關(guān)閉雏搂,任務(wù)調(diào)度現(xiàn)場的數(shù)據(jù)并不會丟失
② 靈活的應(yīng)用方式,運(yùn)行定義觸發(fā)器的調(diào)度時間表寇损,并可以對觸發(fā)器和任務(wù)進(jìn)行關(guān)聯(lián)映射凸郑,Quartz提供了組件式的監(jiān)聽器,支持任務(wù)和調(diào)度的多種組合方式矛市,支持調(diào)度數(shù)據(jù)的多種存儲方式
③ 分布式和集群能力芙沥,Terracotta 收購后在原來功能基礎(chǔ)上作了進(jìn)一步提升。

Quartz核心概念

scheduler:任務(wù)調(diào)度器
trigger:觸發(fā)器浊吏,用于定義任務(wù)調(diào)度時間規(guī)則
job:任務(wù)而昨,即被調(diào)度的任務(wù)

Quartz任務(wù)調(diào)度基本實(shí)現(xiàn)原理

Quartz 任務(wù)調(diào)度的核心元素是 scheduler, triggerjob,其中 triggerjob 是任務(wù)調(diào)度的元數(shù)據(jù)找田, scheduler 是實(shí)際執(zhí)行調(diào)度的控制器歌憨。

trigger 是用于定義調(diào)度時間的元素,即按照什么時間規(guī)則去執(zhí)行任務(wù)墩衙。
Quartz 中主要提供了四種類型的 trigger:SimpleTrigger务嫡,CronTirggerDateIntervalTrigger漆改,和 NthIncludedDayTrigger心铃。這四種 trigger 可以滿足企業(yè)應(yīng)用中的絕大部分需求。

job用于表示被調(diào)度的任務(wù)挫剑。
主要有兩種類型的 job:無狀態(tài)的(stateless)和有狀態(tài)的(stateful)
對于同一個trigger來說于个,有狀態(tài)的 job 不能被并行執(zhí)行,只有上一次觸發(fā)的任務(wù)被執(zhí)行完之后暮顺,才能觸發(fā)下一次執(zhí)行厅篓。
Job 主要有兩種屬性:volatilitydurability,其中 volatility 表示任務(wù)是否被持久化到數(shù)據(jù)庫存儲捶码,而 durability 表示在沒有trigger關(guān)聯(lián)的時候任務(wù)是否被保留羽氮。兩者都是在值為true的時候任務(wù)被持久化或保留。
一個job可以被多個trigger關(guān)聯(lián)惫恼,但是一個trigger只能關(guān)聯(lián)一個job档押。

scheduler
scheduler 工廠創(chuàng)建:DirectSchedulerFactory 或者 StdSchedulerFactory
StdSchedulerFactory 使用較多祈纯,因?yàn)?DirectSchedulerFactory 使用起來不夠方便令宿,需要作許多詳細(xì)的手工編碼設(shè)置。
Scheduler 主要有三種:RemoteMBeanScheduler腕窥, RemoteSchedulerStdScheduler粒没。

在maven項(xiàng)目中使用Quartz

pom.xml文件中添加quartz的依賴:
版本(Apr 19, 2017)

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

首先我們需要定義一個任務(wù)類,該類需要繼承Job類簇爆,
添加execute(JobExecutionContext context)方法癞松,在這個方法中就是我們具體的任務(wù)執(zhí)行的地方爽撒。

public class HelloJob implements Job{
    @Override
    public void execute(JobExecutionContext jobExecutionContext) 
                    throws JobExecutionException {
        SimpleDateFormat df = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
        System.out.println(df.format(System.currentTimeMillis()));
    }
}

任務(wù)調(diào)度

public class APP {
    public static void main(String[] args) throws 
                                    SchedulerException, InterruptedException {
        // 獲取Scheduler實(shí)例
        Scheduler scheduler = StdSchedulerFactory.getDefaultScheduler();
        scheduler.start();
        //具體任務(wù).
        JobDetail jobDetail = JobBuilder.newJob(HelloJob.class)
                                          .withIdentity("job1","group1").build();

        //觸發(fā)時間點(diǎn). (每5秒執(zhí)行1次.)
        SimpleScheduleBuilder simpleScheduleBuilder = SimpleScheduleBuilder
        .simpleSchedule().withIntervalInSeconds(5).repeatForever();
        Trigger trigger = TriggerBuilder.newTrigger()
                           .withIdentity("trigger1","group1") .startNow()
                           .withSchedule(simpleScheduleBuilder).build();
        // 交由Scheduler安排觸發(fā)
        scheduler.scheduleJob(jobDetail,trigger);
    }
}

測試結(jié)果:

QQ截圖20180609210103.png

結(jié)果就是每隔5秒輸出一次時間此外還會輸出關(guān)于觸發(fā)器的一些信息

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市响蓉,隨后出現(xiàn)的幾起案子硕勿,更是在濱河造成了極大的恐慌,老刑警劉巖枫甲,帶你破解...
    沈念sama閱讀 218,941評論 6 508
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件源武,死亡現(xiàn)場離奇詭異,居然都是意外死亡想幻,警方通過查閱死者的電腦和手機(jī)粱栖,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,397評論 3 395
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來举畸,“玉大人查排,你說我怎么就攤上這事〕冢” “怎么了跋核?”我有些...
    開封第一講書人閱讀 165,345評論 0 356
  • 文/不壞的土叔 我叫張陵,是天一觀的道長叛买。 經(jīng)常有香客問我砂代,道長,這世上最難降的妖魔是什么率挣? 我笑而不...
    開封第一講書人閱讀 58,851評論 1 295
  • 正文 為了忘掉前任刻伊,我火速辦了婚禮,結(jié)果婚禮上椒功,老公的妹妹穿的比我還像新娘捶箱。我一直安慰自己,他們只是感情好动漾,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,868評論 6 392
  • 文/花漫 我一把揭開白布丁屎。 她就那樣靜靜地躺著,像睡著了一般旱眯。 火紅的嫁衣襯著肌膚如雪晨川。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 51,688評論 1 305
  • 那天删豺,我揣著相機(jī)與錄音共虑,去河邊找鬼。 笑死呀页,一個胖子當(dāng)著我的面吹牛妈拌,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播赔桌,決...
    沈念sama閱讀 40,414評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼供炎,長吁一口氣:“原來是場噩夢啊……” “哼匪补!你這毒婦竟也來了奖恰?” 一聲冷哼從身側(cè)響起楼肪,我...
    開封第一講書人閱讀 39,319評論 0 276
  • 序言:老撾萬榮一對情侶失蹤袄膏,失蹤者是張志新(化名)和其女友劉穎彭则,沒想到半個月后凤藏,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體忙菠,經(jīng)...
    沈念sama閱讀 45,775評論 1 315
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡燥滑,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,945評論 3 336
  • 正文 我和宋清朗相戀三年雹洗,在試婚紗的時候發(fā)現(xiàn)自己被綠了香罐。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 40,096評論 1 350
  • 序言:一個原本活蹦亂跳的男人離奇死亡时肿,死狀恐怖庇茫,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情螃成,我是刑警寧澤旦签,帶...
    沈念sama閱讀 35,789評論 5 346
  • 正文 年R本政府宣布,位于F島的核電站寸宏,受9級特大地震影響宁炫,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜氮凝,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,437評論 3 331
  • 文/蒙蒙 一羔巢、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧罩阵,春花似錦竿秆、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,993評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至常摧,卻和暖如春搅吁,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背落午。 一陣腳步聲響...
    開封第一講書人閱讀 33,107評論 1 271
  • 我被黑心中介騙來泰國打工谎懦, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人溃斋。 一個月前我還...
    沈念sama閱讀 48,308評論 3 372
  • 正文 我出身青樓界拦,卻偏偏與公主長得像,于是被迫代替她去往敵國和親梗劫。 傳聞我的和親對象是個殘疾皇子享甸,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 45,037評論 2 355

推薦閱讀更多精彩內(nèi)容