Java實現定時任務的幾種方式

目前主要有以下幾種實現方式:
    - JDK自帶 :JDK自帶的Timer以及JDK1.5+ 新增的ScheduledExecutorService;
    - Quartz :簡單卻強大的JAVA作業調度框架;
    - Spring3.0以後自帶的task :可以將它看成一個輕量級的Quartz,而且使用起來比Quartz簡單許多。

一、JDK 自帶的定時器實現

  • Timer類

這個類允許你調度一個java.util.TimerTask任務。主要有以下幾個方法:

// schedule(TimerTask task, long delay) 延遲 delay 毫秒 執行
public static void main(String[] args) {
    for (int i = 0; i < 10; ++i) {
        new Timer("timer - " + i).schedule(new TimerTask() 
            @Override
            public void run() {
                println(Thread.currentThread().getName() + " run ");
            }
        }, 1000);
    }
}

/*
out :
timer - 2 run 
timer - 1 run 
timer - 0 run 
timer - 3 run 
timer - 9 run 
timer - 4 run 
timer - 8 run 
timer - 5 run 
timer - 6 run 
timer - 7 run 
*/

// schedule(TimerTask task, Date time) 特定時間執行
public static void main(String[] args) {
    for (int i = 0; i < 10; ++i) {
        new Timer("timer - " + i).schedule(new TimerTask() {
            @Override
            public void run() {
                println(Thread.currentThread().getName() + " run ");
            }
        }, new Date(System.currentTimeMillis() + 2000));
    }
}
/*
out:
timer - 0 run 
timer - 7 run 
timer - 6 run 
timer - 8 run 
timer - 3 run 
timer - 5 run 
timer - 2 run 
timer - 1 run 
timer - 4 run 
timer - 9 run 
*/

// schedule(TimerTask task, long delay, long period) 延遲 delay 執行並每隔period 執行一次
public static void main(String[] args) {
    for (int i = 0; i < 10; ++i) {
        new Timer("timer - " + i).schedule(new TimerTask() {
            @Override
            public void run() {
                println(Thread.currentThread().getName() + " run ");
            }
        }, 2000 , 3000);
    }
}
/*
out:
timer - 0 run 
timer - 5 run 
timer - 4 run 
timer - 8 run 
timer - 3 run 
timer - 2 run 
timer - 1 run 
timer - 7 run 
timer - 9 run 
timer - 6 run 
timer - 3 run 
timer - 7 run 
timer - 5 run 
timer - 4 run 
timer - 8 run 
*/
  • ScheduledExecutorService 接口實現類

ScheduledExecutorService 是JAVA 1.5 後新增的定時任務接口,主要有以下幾個方法。

- ScheduledFuture<?> schedule(Runnable command,long delay, TimeUnit unit);
       - <V> ScheduledFuture<V> schedule(Callable<V> callable,long delay, TimeUnit unit);
       - ScheduledFuture<?> scheduleAtFixedRate(Runnable command,long initialDelay,long period,TimeUnitunit);
       - ScheduledFuture<?> scheduleWithFixedDelay(Runnable command,long initialDelay,long delay,TimeUnitunit);

默認實現爲ScheduledThreadPoolExecutor 繼承了ThreadPoolExecutor 的線程池特性,配合future特性,比Timer更強大。 具體用法可以閱讀JDK文檔;Spring Task內部也是依靠它實現的。示例代碼:

public static void main(String[] args) throws SchedulerException {
    ScheduledThreadPoolExecutor executor = (ScheduledThreadPoolExecutor)Executors.newScheduledThreadPool(10);
    for (int i = 0; i < 10; ++i) {
        executor.schedule(new Runnable() {
            @Override
            public void run() {
                System.out.println(Thread.currentThread().getName() + " run ");
            }
        } , 2 , TimeUnit.SECONDS);
    }
    executor.shutdown();
}
/*
out:
pool-1-thread-2 run 
pool-1-thread-5 run 
pool-1-thread-4 run 
pool-1-thread-3 run 
pool-1-thread-8 run 
pool-1-thread-5 run 
pool-1-thread-7 run 
pool-1-thread-2 run 
pool-1-thread-1 run 
pool-1-thread-6 run 
*/

二、Quartz 定時器實現

Quartz是一個完全由Java編寫的開源作業調度框架,爲在Java應用程序中進行作業調度提供了簡單卻強大的機制。Quartz允許開發人員根據時間間隔來調度作業。它實現了作業和觸發器的多對多的關係,還能把多個作業與不同的觸發器關聯。可以動態的添加刪除定時任務,另外很好的支撐集羣調度。簡單地創建一個org.quarz.Job接口的Java類,Job接口包含唯一的方法:

public void execute(JobExecutionContext context) throws JobExecutionException;

在Job接口實現類裏面,添加需要的邏輯到execute()方法中。配置好Job實現類並設定好調度時間表(Trigger),Quartz就會自動在設定的時間調度作業執行execute()。

整合了Quartz的應用程序可以重用不同事件的作業,還可以爲一個事件組合多個作業。Quartz通過屬性文件來配置JDBC事務的數據源、全局作業、觸發器偵聽器、插件、線程池等等。(quartz.properties)

通過maven引入依賴(這裏主要介紹2.3.0) 注意:shiro-scheduler中依賴的是1.x版本,如果同時使用會衝突。

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

創建Job類

public class TestJob implements Job{
    @Override
    public void execute(JobExecutionContext jobExecutionContext) throws JobExecutionException {
        println(Thread.currentThread().getName() + " test job begin " + DateUtil.getCurrentTimeStr());
    }
}

調度任務

public static void main(String[] args) throws InterruptedException, SchedulerException {

        Scheduler scheduler = new StdSchedulerFactory().getScheduler();
        // 開始
        scheduler.start();
        // job 唯一標識 test.test-1
        JobKey jobKey = new JobKey("test" , "test-1");
        JobDetail jobDetail = JobBuilder.newJob(TestJob.class).withIdentity(jobKey).build();
        Trigger trigger = TriggerBuilder.newTrigger()
                .withIdentity("test" , "test")
                // 延遲一秒執行
                .startAt(new Date(System.currentTimeMillis() + 1000))
                // 每隔一秒執行 並一直重複
        .withSchedule(SimpleScheduleBuilder.simpleSchedule().withIntervalInSeconds(1).repeatForever())
                .build();
        scheduler.scheduleJob(jobDetail , trigger);

        Thread.sleep(5000);
        // 刪除job
        scheduler.deleteJob(jobKey);
    }

/*
out :
DefaultQuartzScheduler_Worker-1test job begin 2017-06-03 14:30:33
DefaultQuartzScheduler_Worker-2test job begin 2017-06-03 14:30:34
DefaultQuartzScheduler_Worker-3test job begin 2017-06-03 14:30:35
DefaultQuartzScheduler_Worker-4test job begin 2017-06-03 14:30:36
DefaultQuartzScheduler_Worker-5test job begin 2017-06-03 14:30:37
*/

Quartz 主要包含以下幾個部分:

Job:是一個接口,只有一個方法void execute(JobExecutionContextcontext),開發者實現該接口定義運行任務,JobExecutionContext類提供了調度上下文的各種信息。Job運行時的信息保存在JobDataMap實例中;

JobDetail:Quartz在每次執行Job時,都重新創建一個Job實例,所以它不直接接受一個Job的實例,相反它接收一個Job實現類,以便運行時通過newInstance()的反射機制實例化Job。因此需要通過一個類來描述Job的實現類及其它相關的靜態信息,如Job名字、描述、關聯監聽器等信息,JobDetail承擔了這一角色。

Trigger:是一個類,描述觸發Job執行的時間觸發規則。主要有SimpleTrigger和CronTrigger這兩個子類。當僅需觸發一次或者以固定時間間隔週期執行,SimpleTrigger是最適合的選擇;而CronTrigger則可以通過Cron表達式定義出各種複雜時間規則的調度方案:如每早晨9:00執行,週一、週三、週五下午5:00執行等;

Calendar:org.quartz.Calendar和java.util.Calendar不同,它是一些日曆特定時間點的集合(可以簡單地將org.quartz.Calendar看作java.util.Calendar的集合——java.util.Calendar代表一個日曆時間點,無特殊說明後面的Calendar即指org.quartz.Calendar)。一個Trigger可以和多個Calendar關聯,以便排除或包含某些時間點。假設,我們安排每週星期一早上10:00執行任務,但是如果碰到法定的節日,任務則不執行,這時就需要在Trigger觸發機制的基礎上使用Calendar進行定點排除。

Scheduler:代表一個Quartz的獨立運行容器,Trigger和JobDetail可以註冊到Scheduler中,兩者在Scheduler中擁有各自的組及名稱,組及名稱是Scheduler查找定位容器中某一對象的依據,Trigger的組及名稱必須唯一,JobDetail的組和名稱也必須唯一(但可以和Trigger的組和名稱相同,因爲它們是不同類型的)。Scheduler定義了多個接口方法,允許外部通過組及名稱訪問和控制容器中Trigger和JobDetail。

Scheduler可以將Trigger綁定到某一JobDetail中,這樣當Trigger觸發時,對應的Job就被執行。一個Job可以對應多個Trigger,但一個Trigger只能對應一個Job。可以通過SchedulerFactory創建一個Scheduler實例。Scheduler擁有一個SchedulerContext,它類似於ServletContext,保存着Scheduler上下文信息,Job和Trigger都可以訪問SchedulerContext內的信息。SchedulerContext內部通過一個Map,以鍵值對的方式維護這些上下文數據,SchedulerContext爲保存和獲取數據提供了多個put()和getXxx()的方法。可以通過Scheduler#

getContext()獲取對應的SchedulerContext實例;

ThreadPool:Scheduler使用一個線程池作爲任務運行的基礎設施,任務通過共享線程池中的線程提高運行效率。

三、Spring 相關的任務調度

Spring 3.0+ 自帶的任務調度實現,主要依靠TaskScheduler接口的幾個實現類實現。刪除和修改任務比較麻煩。
主要用法有以下三種:

  • Spring配置文件實現
  • 註解實現
  • 代碼動態添加

配置文件實現 spring-schedule.xml

<task:scheduler id="myScheduler" pool-size="10" />
<task:scheduled-tasks scheduler="myScheduler">
    <task:scheduled ref="job" method="test" cron="0 * * * * ?"/>
</task:scheduled-tasks>

註解實現 spring-schedule.xml

<task:scheduler id="myScheduler" pool-size="10" />
// 啓用註解
<task:annotation-driven scheduler="myScheduler"/> 
@Component  
public class Task{  

    @Scheduled(cron="0/5 * *  * * ? ")   //每5秒執行一次       
    public void execute(){     
        DateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");    
        System.out.println(sdf.format(DateTime.now().toDate())+"*********B任務每5秒執行一次進入測試");      
    }      
}  

代碼動態添加 spring-schedule.xml

<bean id = "myScheduler" class="org.springframework.scheduling.concurrent.ThreadPoolTaskScheduler">
        <property name="poolSize" value="10"/>
        <property name="threadGroupName" value="myScheduler" />
        <property name="threadNamePrefix" value="-1" />
</bean>
<task:annotation-driven scheduler="myScheduler"/> 
@Component
public class Test {

    @Autowired
    private ThreadPoolTaskScheduler myScheduler;

    public void addJob(){

        myScheduler.schedule(new Runnable() {
            @Override
            public void run() {
                System.out.println(Thread.currentThread().getName() + " run ");
            }
        } , new CronTrigger("0/5 * *  * * ? ")); //每5秒執行一次
    }
}

spring 結合 quartz 實現任務調度

  • spring 配置文件 spring-quartz.xml
<bean id="quartzsScheduler" class="org.springframework.scheduling.quartz.SchedulerFactoryBean" lazy-init="false">
        <property name="triggers">
            <list>
            <ref bean="testTrigger" />
            </list>
        </property>
</bean>

<!-- jobClass需要繼承QuartzJobBean  也可以使用 MethodInvokingJobDetailFactoryBean 定義任意類任意方法爲Job-->
<bean id="testJobDetail" class="org.springframework.scheduling.quartz.JobDetailFactoryBean">
              <property name="jobClass">
                     <value>com.test.TestJob</value>
              </property>
              <property name="durability" value="true" />
              <!-- requestsRecovery屬性必須設置爲 true,當Quartz服務被中止後,再次啓動或集羣中其他機器接手任務時會嘗試恢復執行之前未完成的所有任務 -->
              <property name="requestsRecovery" value="true" />
</bean>
<bean id="testTrigger" class="org.springframework.scheduling.quartz.CronTriggerFactoryBean">
              <property name="jobDetail" ref="testJobDetail" />
              <property name="cronExpression" value="0 0 10 * * ?" />
</bean>

動態增加刪除

@Component
public class Test {

    @Autowired
    private SchedulerFactoryBean quartzScheduler;

    public void addJob() throws SchedulerException {

        Scheduler scheduler = quartzScheduler.getScheduler();
        JobKey jobKey = new JobKey("test", "test");
        if (scheduler.checkExists(jobKey)) {
            return;
        }
        JobDetail jobDetail = JobBuilder.newJob(TestJob.class).withIdentity(jobKey).build();
        Trigger trigger = TriggerBuilder.newTrigger().withIdentity("test", "test")
                .withSchedule(SimpleScheduleBuilder.simpleSchedule().withIntervalInSeconds(1).repeatForever()).build();
        scheduler.scheduleJob(jobDetail, trigger);
    }
}

以上僅僅是對自己學習的總結,深入瞭解還需查找相關資料。比如動態增加,修改定時任務。以及Quartz的集羣模式等。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章