天天看點

Java中定時任務排程的實作

在工作中遇到一個需求,需要定時自動執行某項功能,這就需要用到定時任務了。

定時任務排程:基于給定的時間點,給定的時間間隔或者給定的執行次數自動執行任務。

定時任務排程的幾種實作方式:

Timer:

Timer由JDK自帶,不需要引入多餘的jar。Java自帶的

java.util.Timer

類,這個類允許你排程一個

java.util.TimerTask

任務。Timer隻有一個背景線程執行任務,使用這種方式可以讓你的程式按照某一個頻度執行,但不能在指定時間運作。一般用的較少。

Quartz:

使用Quartz,這是一個功能比較強大的的排程器,可以讓你的程式在指定時間執行,也可以按照某一個頻度執行,配置起來稍顯複雜,Quartz擁有背景執行線程池能夠使用多個線程。

Spring Task:

Spring3.0以後自帶的task,可以将它看成一個輕量級的Quartz,而且使用起來比Quartz簡單許多。

Timer代碼實作

首先寫個需要定時實作的邏輯,這裡是簡單輸出格式化後的時間

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()));
    }
}
           

然後寫定時任務排程類

public class MyTimer {
    public static void main(String[] args){
        //建立timer執行個體
        Timer timer=new Timer();
        //建立一個MyTimerTask執行個體
        MyTimerTask myTimerTask=new MyTimerTask();
        //通過timer定時調用myTimerTask中的run方法,因為 TimerTask 類實作了 Runnable 接口。
        timer.schedule(myTimerTask,10*1000,2*1000);
    }
}
           

QQ截圖20180609173715.png

這裡的重點是Timer中schedule()方法的三個參數

第一個參數:是 就是我們剛剛寫的

MyTimerTask

類,這裡需要繼承

TimerTask

,并實作

run()

方法,因為

TimerTask

類實作了

Runnable

接口。

第二個參數:是程式啟動後,timer定時器第一次調用run方法的時間,0表示不指時間,立刻調用。(10*1000表示10秒,因為參數機關是毫秒)

第三個參數:是指第一次調用之後,從第二次開始每隔多長的時間調用一次 run() 方法。機關同樣是毫秒。

拓展:

QQ圖檔20180609191051.png

其實Timer中schedule()方法一共有四種用法,剛剛隻是其中一種

schedule(task, time):time為

Date

類型,在指定時間執行一次。

schedule(task, firstTime, period):firstTime為

Date

類型,從

firstTime

時刻開始,每隔

period

毫秒執行一次。

schedule(task, delay):從現在起過

delay

毫秒執行一次

QQ截圖20180609194229.png

scheduleAtFixedRate(task,delay,period)

schedule(task,delay,period)

基本一樣

scheduleAtFixedRate(task,firstTime,period)

schedule(task,firstTime,period)

差別可以參考:

https://blog.csdn.net/gtuu0123/article/details/6040159
測試結果:

QQ截圖20180609174352.png

每隔兩秒輸出一次時間,第二個參數在截圖上展現不了,需要自己敲出來感受。

注:時效性要求較高的多任務并發作業,複雜的任務的排程不推薦使用

Spring Task

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

結果如下

Paste_Image.png

spring task 在計算時間的時候,是根據目前伺服器的系統時間進行計算.

如果是每10秒執行一次的話,那麼它是從系統時間的0,10,20秒進行計算的.

如果是每1分鐘執行一次的話,那麼它是從系統時間的1分鐘,2分鐘進行計算的

如何動态修改

cron

參數

代碼解釋 一開始設定的是每20秒執行一次,隻要通路下

http://localhost:8080/changeExpression

就可以改成每10秒執行一次了

@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類型的資料

看起來很多代碼,其實就是先實作

SchedulingConfigurer

實作裡面的方法,縮小成就這樣

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

項目需求:

如果有個這樣的場景,在某一段時間内要執行特定的操作

@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);
    }
}
           

把時間轉化成時間戳,再去對比時間

動态添加修改删除定時任務

1.首先需要重新認識一個類

ThreadPoolTaskScheduler

:線程池任務排程類,能夠開啟線程池進行任務排程。

2.

ThreadPoolTaskScheduler.schedule()

方法會建立一個定時計劃

ScheduledFuture

,在這個方法需要添加兩個參數,

Runnable

(線程接口類) 和

CronTrigger

(定時任務觸發器)

3.在

ScheduledFuture

中有一個cancel可以停止定時任務。

@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

方法中可以寫入具體的定時任務邏輯

(a)我們首先了一個類

DynamicTask

;

(b)定義了兩個變量,

threadPoolTaskScheduler

future

其中

future

treadPoolTaskScheduler

執行方法

schedule

的傳回值,主要用于定時任務的停止。

(c)編寫啟動定時器的方法

startCron()

(d)編寫停止方法

stopCron()

,這裡編碼的時候,需要注意下需要判斷下

future

null

的時候,不然就很容易抛出

NullPointerException

(e)編寫修改定時任務執行周期方法

changeCron10()

,這裡的原理就是關閉之前的定時器,創新在建立一個新的定時器。

Quartz

Quartz是OpenSymphony開源組織在Job scheduling領域又一個開源項目,是一個完全由Java編寫的開源作業排程架構。

官網位址:

http://www.quartz-scheduler.org/

為在Java應用程式中進行作業排程提供了簡單卻強大的機制。Quartz允許開發人員根據時間間隔來排程作業。它實作了作業和觸發器的多對多的關系,還能把多個作業與不同的觸發器關聯。

Quartz的特點

① 強大的排程功能,spring預設的排程架構, Quartz 很容易與 Spring 內建實作靈活可配置的排程功能,及時系統因故障關閉,任務排程現場的資料并不會丢失

② 靈活的應用方式,運作定義觸發器的排程時間表,并可以對觸發器和任務進行關聯映射,Quartz提供了元件式的監聽器,支援任務和排程的多種組合方式,支援排程資料的多種存儲方式

③ 分布式和叢集能力,Terracotta 收購後在原來功能基礎上作了進一步提升。

Quartz核心概念

scheduler:任務排程器

trigger:觸發器,用于定義任務排程時間規則

job:任務,即被排程的任務

Quartz任務排程基本實作原理

Quartz 任務排程的核心元素是

scheduler

,

trigger

job

,其中

trigger

job

是任務排程的中繼資料,

scheduler

是實際執行排程的控制器。

trigger 是用于定義排程時間的元素,即按照什麼時間規則去執行任務。

Quartz 中主要提供了四種類型的 trigger:

SimpleTrigger

CronTirgger

DateIntervalTrigger

,和

NthIncludedDayTrigger

。這四種 trigger 可以滿足企業應用中的絕大部分需求。

job用于表示被排程的任務。

主要有兩種類型的 job:無狀态的(stateless)和有狀态的(stateful)

對于同一個

trigger

來說,有狀态的

job

不能被并行執行,隻有上一次觸發的任務被執行完之後,才能觸發下一次執行。

Job 主要有兩種屬性:

volatility

durability

volatility

表示任務是否被持久化到資料庫存儲,而

durability

表示在沒有

trigger

關聯的時候任務是否被保留。兩者都是在值為

true

的時候任務被持久化或保留。

一個

job

可以被多個

trigger

關聯,但是一個

trigger

隻能關聯一個

job

scheduler

scheduler

工廠建立:

DirectSchedulerFactory

或者

StdSchedulerFactory

StdSchedulerFactory

使用較多,因為

DirectSchedulerFactory

使用起來不夠友善,需要作許多詳細的手工編碼設定。

Scheduler

主要有三種:

RemoteMBeanScheduler

RemoteScheduler

StdScheduler

在maven項目中使用Quartz

pom.xml

檔案中添加

quartz

的依賴:

版本(Apr 19, 2017)

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

首先我們需要定義一個任務類,該類需要繼承

Job

類,

添加

execute(JobExecutionContext context)

方法,在這個方法中就是我們具體的任務執行的地方。

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()));
    }
}
           

任務排程

public class APP {
    public static void main(String[] args) throws 
                                    SchedulerException, InterruptedException {
        // 擷取Scheduler執行個體
        Scheduler scheduler = StdSchedulerFactory.getDefaultScheduler();
        scheduler.start();
        //具體任務.
        JobDetail jobDetail = JobBuilder.newJob(HelloJob.class)
                                          .withIdentity("job1","group1").build();

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

QQ截圖20180609210103.png

結果就是每隔5秒輸出一次時間此外還會輸出關于觸發器的一些資訊