本文列舉常見的java定時任務實現方式,並做一定比較。
1. 循環內部sleep實現周期執行
創建一個thread,run() while循環里sleep()來實現周期性執行; 簡單粗暴,作為一個初學者很容易想到。
public class Task1 {
public static void main(String[] args) {
// run in a second
final long timeInterval = 1000;
Runnable runnable = new Runnable() {
public void run() {
while (true) {
System.out.println("Hello !!");
// 使用線程休眠來實現周期執行,
try {
Thread.sleep(timeInterval);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
};
Thread thread = new Thread(runnable);
thread.start();
}
}
2. 使用Timer類調度TimerTask任務
改進:當啟動和去取消任務時可以控制; 第一次執行任務時可以指定你想要的delay時間
不足:
- Timer的調度是基於絕對時間的,所以當系統時間改變時會影響Timer。
- Timer只有一個工作線程,所以當一個任務執行時間很長的時候,會影響后續任務的調度。
而ScheduledThreadPoolExecutor通過線程池的方式配置更靈活。 - 如果任務拋出了一個未檢查的異常,將會導致Timer的工作線程被終止,使Timer無法在繼續運行。
import java.util.Timer;
import java.util.TimerTask;
public class HelperTest {
public static void main(String[] args) {
// 具體任務。
TimerTask task = new TimerTask() {
@Override
public void run() {
// task to run goes here
System.out.println("Hello !!!");
}
};
// Timer類可以調度任務。 Timer實例可以調度多任務,它是線程安全的。
Timer timer = new Timer();
long delay = 0;
long intevalPeriod = 1 * 1000;
// schedules the task to be run in an interval
timer.scheduleAtFixedRate(task, delay, intevalPeriod);
}
}
3. 使用j.u.c.ScheduledExecutorService定時任務接口
- 相比於Timer的單線程,它是通過線程池的方式來執行任務的
- 可以靈活的設定第一次執行任務delay時間
- 提供了良好的約定,以便設定執行的時間間隔
import java.util.concurrent.Executors;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.TimeUnit;
public class Task3 {
public static void main(String[] args) {
ScheduledExecutorService service = new ScheduledThreadPoolExecutor(1);
// 初始化延遲0ms開始執行,每隔200ms重新執行一次任務。
ScheduledExecutorService pool = new ScheduledThreadPoolExecutor(1);
pool.scheduleAtFixedRate(new Runnable() {
@Override
public void run() {
// task to run goes here
System.out.println("Hello !");
}
}, 0, 200L, TimeUnit.MILLISECONDS);
}
實現類使用的是ScheduledThreadPoolExecutor。該類繼承自ThreadPoolExecutor read more,阻塞隊列使用的是DelayedWorkQueue,是ScheduledThreadPoolExecutor的內部類。
ScheduledExecutorService接口方法說明:
其中scheduleAtFixedRate和scheduleWithFixedDelay在實現定時程序時比較方便。
-
scheduleAtFixedRate(runnable, 0, 200L, TimeUnit.MILLISECONDS) 按指定周期執行某個任務
初始化延遲0ms開始執行,每隔200ms重新執行一次任務。 -
scheduleWithFixedDelay(runnable, 0, 200L, TimeUnit.MILLISECONDS) 按指定間隔執行某個任務
初始化時延時0ms開始執行,下次執行時間是(本次執行結束 + 延遲200ms)后開始執行。 -
schedule(Runnable command, long delay, TimeUnit unit) 在delay延時后執行一次性任務
備注:對於scheduleAtFixedRate,實際上如果當前線程阻塞執行時間t > 設置的間隔時間period,下次是在t時間后執行,並非period時間后立即開始。
ScheduledExecutorService的spring配置
>> spring.xml
<bean id="gkHeartBeatScheduler" class="org.springframework.scheduling.concurrent.ScheduledExecutorFactoryBean">
<property name="poolSize" value="4"/>
<property name="threadNamePrefix" value="gkHeartBeat"/>
</bean>
>> xxx.java
@Autowired
@Qualifier("gkHeartBeatScheduler")
ScheduledExecutorService scheduledExecutorService;
scheduledExecutorService.scheduleAtFixedRate(
new Runnable() {
@Override
public void run() {
System.out.println("do sth");
}
}, 1l, 2l, TimeUnit.SECONDS);
spring ScheduledExecutorFactoryBean內部同樣使用的ScheduledThreadPoolExecutor,並對其做了包裝處理。
public class ScheduledExecutorFactoryBean extends ExecutorConfigurationSupport implements FactoryBean<ScheduledExecutorService>
4. @Sheduled注解方式
@Sheduled內部也使用了ScheduledThreadPoolExecutor。具體源代碼可參見:spring-context包中的ScheduledAnnotationBeanPostProcessor。
用法就很簡單了,舉例:
- pom文件引入spring-context依賴
- 使用注解方式配置定時任務即可
import org.springframework.scheduling.annotation.EnableScheduling;
import org.springframework.scheduling.annotation.Scheduled;
import org.springframework.stereotype.Component;
@Component
@EnableScheduling
public class ScheduledAnnotationDemo {
// @Scheduled和觸發器元素一起添加到方法上.
@Scheduled(fixedDelay=5000)
public void doSomething() {
System.out.println("like scheduleWithFixedDelay");
}
@Scheduled(fixedRate=5000)
public void doSomething() {
System.out.println("like scheduleAtFixedRate");
}
// fixed-delay、fixed-rate任務都可以設置初始delay。
@Scheduled(initialDelay=1000, fixedRate=5000)
public void doSomething() {
// something that should execute periodically
}
// 也支持cron表達式
@Scheduled(cron = "0/5 * * * * ?")
public void doSomething() {
// something that should execute on weekdays only
System.out.println("5s執行一次");
}
//cron舉例:(秒 - 分 - 時 - 日 - 月- 星期)
// */5 * * * * ? 每隔5秒執行一次
// 0 */1 * * * ? 每隔1分鍾執行一次
// 0 0 1 * * ? 每天1點執行一次
// 0 0 1 1 * ? 每月1號1點執行一次
// 0 0 1 L * ? 每月最后一天1點執行一次
// 0 0 1 ? * L 每周星期天1點執行一次
}
上面使用@EnableScheduling的方式啟動定時任務,等價於在spring xml中配置<task:annotation-driven />
元素。
5. 開源任務調度框架Quartz
Quartz , 功能強大的任務調度庫。適用於具有更復雜調度要求的場景。
提供了對持久化任務調度信息、事務、分布式的支持。與spring無縫對接。
參見:quartz調度基礎: Job/Trigger/Schedule.
6. 小結
- 使用ScheduledThreadPoolExecutor完成簡單定時任務,是比較理想和常用的實現方式。書寫時更容易理解其過程實現。
- 也可以用@Sheduled注解的形式,更加輕量化,看起來更簡潔。
- 對復雜的任務調度,可以使用Quartz框架。