1. 程式人生 > >Java 定時任務的幾種實現方式

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

java作業調度 tails 監聽器 ever 觸發 posit exist ttr 輕量級

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

@(JAVA)[spring|quartz|定時器]
  近期項目開發中需要動態的添加定時任務,比如在某個活動結束時,自動生成獲獎名單,導出excel等,此類任務由於活動時間是動態的,不能把定時任務配置在配置文件或寫死在代碼中。當然也可以增加一個定時掃描的任務來實現。借此機會整理了AVA實現定時任務的幾種常用方式,以下做簡要介紹。
目前主要有以下幾種實現方式:
- JDK自帶 :JDK自帶的Timer以及JDK1.5+ 新增的ScheduledExecutorService;
- Quartz :簡單卻強大的JAVA作業調度框架
- Spring3.0以後自帶的task :可以將它看成一個輕量級的Quartz,而且使用起來比Quartz簡單許多;
下面將一一介紹以上三種實現方式。


  • JAVA實現定時任務的幾種方式
    • JDK 自帶的定時器實現
    • Quartz 定時器實現
    • Spring 相關的任務調度

JDK 自帶的定時器實現

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

1. 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 

2. 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 

3. 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 
  • 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
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55
  • 56
  • 57
  • 58
  • 59
  • 60
  • 61
  • 62
  • 63
  • 64
  • 65
  • 66
  • 67
  • 68
  • 69
  • 70
  • 71
  • 72
  • 73
  • 74
  • 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);
  • 1
  • 2
  • 3
  • 4

默認實現為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 
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24

Quartz 定時器實現

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

public void execute(JobExecutionContext context) throws JobExecutionException;
  • 1
  • 2

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

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

  1. 通過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>
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  1. 創建Job類
public class TestJob implements Job{
    @Override
    public void execute(JobExecutionContext jobExecutionContext) throws JobExecutionException {
        println(Thread.currentThread().getName() + " test job begin " + DateUtil.getCurrentTimeStr());
    }
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  1. 調度任務
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
  • 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

Quartz 主要包含以下幾個部分

Job:是一個接口,只有一個方法void execute(JobExecutionContext

context),開發者實現該接口定義運行任務,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使用一個線程池作為任務運行的基礎設施,任務通過共享線程池中的線程提高運行效率。

關於簡單使用,可以參考quartz的example,下面鏈接是一些入門幫助。

Quartz定時任務學習(一)簡單任務
Quartz定時任務學習(二)web應用
Quartz定時任務學習(三)屬性文件和jar

深入學習可以閱讀官方文檔和相關博客閱讀
以下為推薦博客地址
quartz詳解2:quartz由淺入深

Spring 相關的任務調度

  1. 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>
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6

註解實現

spring-schedule.xml
<task:scheduler id="myScheduler" pool-size="10" />
// 啟用註解
<task:annotation-driven scheduler="myScheduler"/> 
  • 1
  • 2
  • 3
  • 4
@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秒執行一次進入測試");      
       }      
}  
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9

代碼動態添加

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"/> 
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
@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秒執行一次
    }
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  1. 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>
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21

動態增加刪除

@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);
    }
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19

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

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