Java項目實現(xiàn)定時任務的三種方法
1 使用java.util.Timer
這種方式的定時任務主要用到兩個類,Timer 和 TimerTask,使用起來比較簡單。其中 Timer 負責設定 TimerTask 的起始與間隔執(zhí)行時間。 TimerTask是一個抽象類,new的時候實現(xiàn)自己的 run 方法,然后將其丟給 Timer 去執(zhí)行即可。
代碼示例:
import java.time.LocalDateTime;
import java.util.Timer;
import java.util.TimerTask;
public class Schedule {
public static void main(String[] args) {
TimerTask timerTask = new TimerTask() {
@Override
public void run() {
System.out.println("當前線程:" + Thread.currentThread().getName() + " 當前時間" + LocalDateTime.now());
}
};
// 在指定延遲0毫秒后開始,隨后地執(zhí)行以2000毫秒間隔執(zhí)行timerTask
new Timer().schedule(timerTask, 0L, 2000L);
System.out.println("當前線程:" + Thread.currentThread().getName() + " 當前時間" + LocalDateTime.now());
}
}

缺點:
- Timer 的背后只有一個線程,不管有多少個任務,都只有一個工作線程串行執(zhí)行,效率低下
- 受限于單線程,如果第一個任務邏輯上死循環(huán)了,后續(xù)的任務一個都得不到執(zhí)行
- 依然是由于單線程,任一任務拋出異常后,整個 Timer 就會結束,后續(xù)任務全部都無法執(zhí)行
2 使用ScheduledExecutorService
ScheduledExecutorService 即是 Timer 的替代者,JDK 1.5 并發(fā)包引入,是基于線程池設計的定時任務類。每個調(diào)度任務都會分配到線程池中的某一個線程去執(zhí)行,任務就是并發(fā)調(diào)度執(zhí)行的,任務之間互不影響。
Java 5.0引入了java.util.concurrent包,其中的并發(fā)實用程序之一是ScheduledThreadPoolExecutor ,它是一個線程池,用于以給定的速率或延遲重復執(zhí)行任務。它實際上是Timer/TimerTask組合的更通用替代品,因為它允許多個服務線程,接受各種時間單位,并且不需要子類TimerTask (只需實現(xiàn)Runnable)。使用一個線程配置ScheduledThreadPoolExecutor使其等效于Timer 。
代碼示例:
import java.time.LocalDateTime;
import java.util.concurrent.*;
public class Schedule {
public static void main(String[] args) {
// 創(chuàng)建一個ScheduledThreadPoolExecutor線程池,核心線程數(shù)為5
ScheduledExecutorService scheduledExecutorService = new ScheduledThreadPoolExecutor(5);
// 創(chuàng)建Runnable打印當前線程和當前時間
Runnable r = () -> System.out.println("當前線程:" + Thread.currentThread().getName() + " 當前時間" + LocalDateTime.now());
/**
* schedule:只執(zhí)行一次調(diào)度
* scheduleAtFixedRate:一開始就計算間隔時間,如果任務超過間隔時間,那么就直接開始下一個任務
* scheduleWithFixedDelay:任務無論執(zhí)行多久,都要等待上一輪任務完成之后再間隔指定時間,然后才開始下一個任務
*/
// 在指定1秒延遲后執(zhí)行r,之后每兩秒執(zhí)行一次
scheduledExecutorService.scheduleAtFixedRate(r, 1, 2, TimeUnit.SECONDS);
}
}

3 使用Spring Task
Spring Task 底層是基于 JDK 的 ScheduledThreadPoolExecutor 線程池來實現(xiàn)的。直接通過Spring 提供的 @Scheduled 注解即可定義定時任務,非常方便。
以Spring Boot來作為示例,步驟為
- 在啟動類所在包下創(chuàng)建Schedule 類(在沒有配置@ComponentScan的情況下,Spring Boot只會默認掃描啟動類所在包的spring組件)
- 在該類上添加@Component和@EnableScheduling注解
- 在方法上添加@Scheduled注解,該注解主要參數(shù)如下
String cron() default ""; // 支持cron表達式 long fixedDelay() default -1; // 在最后一次調(diào)用結束和下一次調(diào)用開始之間的時間間隔,以毫秒為單位 String fixedDelayString() default ""; // 同上,類似ScheduledExecutorService的scheduleWithFixedDelay long fixedRate() default -1; // 在調(diào)用之前的時間間隔,以毫秒為單位 String fixedRateString() default ""; // 同上,類似ScheduledExecutorService的scheduleAtFixedRate long initialDelay() default -1; // 在第一次執(zhí)行fixedRate()或fixedDelay()任務之前要延遲的毫秒數(shù) String initialDelayString() default ""; // 同上
代碼示例:
import org.springframework.scheduling.annotation.EnableScheduling;
import org.springframework.scheduling.annotation.Scheduled;
import org.springframework.stereotype.Component;
import java.time.LocalDateTime;
@Component
@EnableScheduling
public class Schedule {
@Scheduled(fixedRate = 2000L)
public void task() {
System.out.println("當前線程:" + Thread.currentThread().getName() + " 當前時間" + LocalDateTime.now());
}
}

優(yōu)點: 簡單,輕量,支持 Cron 表達式缺點 :默認只支持單機,是單線程的,并且提供的功能比較單一
可以通過@EnableAsync和 @Async開啟多線程
import org.springframework.scheduling.annotation.Async;
import org.springframework.scheduling.annotation.EnableAsync;
import org.springframework.scheduling.annotation.EnableScheduling;
import org.springframework.scheduling.annotation.Scheduled;
import org.springframework.stereotype.Component;
import java.time.LocalDateTime;
@Component
@EnableAsync // 開啟異步多線程
@EnableScheduling
public class Schedule {
@Async
@Scheduled(fixedRate = 2000L)
public void task() {
System.out.println("當前線程:" + Thread.currentThread().getName() + " 當前時間" + LocalDateTime.now());
}
}

使用@EnableAsync注解后,默認情況下,Spring將搜索關聯(lián)的線程池定義:上下文中的唯一org.springframework.core.task.TaskExecutor
的bean,或者名為“taskExecutor”的java.util.concurrent.Executor
的bean。如果兩者都無法解析,則將使用org.springframework.core.task.SimpleAsyncTaskExecutor來處理異步方法調(diào)用。

TaskExecutor實現(xiàn)為每個任務啟動一個新線程,異步執(zhí)行它。 支持通過“concurrencyLimit”bean 屬性限制并發(fā)線程。默認情況下,并發(fā)線程數(shù)是無限的,所以使用默認的線程池有導致內(nèi)存溢出的風險。
注意:剛才的運行結果看起來是線程復用的,而實際上此實現(xiàn)不重用線程!應盡量實現(xiàn)一個線程池TaskExecutor ,特別是用于執(zhí)行大量短期任務。不要使用默認的SimpleAsyncTaskExecutor。
import org.springframework.context.annotation.Bean;
import org.springframework.scheduling.annotation.Async;
import org.springframework.scheduling.annotation.EnableAsync;
import org.springframework.scheduling.annotation.EnableScheduling;
import org.springframework.scheduling.annotation.Scheduled;
import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor;
import org.springframework.stereotype.Component;
import java.time.LocalDateTime;
import java.util.concurrent.Executor;
@Component
@EnableAsync
@EnableScheduling
public class Schedule {
@Async
@Scheduled(fixedRate = 2000L)
public void task() {
System.out.println("當前線程:" + Thread.currentThread().getName() + " 當前時間" + LocalDateTime.now());
}
@Bean("taskExecutor")
public Executor taskExecutor() {
ThreadPoolTaskExecutor taskExecutor = new ThreadPoolTaskExecutor();
taskExecutor.setCorePoolSize(10);
taskExecutor.setMaxPoolSize(50);
taskExecutor.setQueueCapacity(200);
taskExecutor.setKeepAliveSeconds(60);
taskExecutor.setThreadNamePrefix("自定義-");
taskExecutor.setAwaitTerminationSeconds(60);
return taskExecutor;
}
}

上面提到的一些定時任務的解決方案都是在單機下執(zhí)行的,適用于比較簡單的定時任務場景比如每天凌晨備份一次數(shù)據(jù)。如果我們需要一些高級特性比如支持任務在分布式場景下的分片和高可用的話,我們就需要用到分布式任務調(diào)度框架了,比如Quartz、Elastic-Job、XXL-JOB、PowerJob,本文就不再詳細進行介紹了,感興趣的可以自行查閱相關資料。
總結
到此這篇關于Java項目實現(xiàn)定時任務的三種方法的文章就介紹到這了,更多相關Java定時任務實現(xiàn)內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關文章希望大家以后多多支持腳本之家!
相關文章
java如何將Object數(shù)組轉換為指定類型數(shù)組
這篇文章主要介紹了java如何將Object數(shù)組轉換為指定類型數(shù)組,具有很好的參考價值,希望對大家有所幫助。如有錯誤或未考慮完全的地方,望不吝賜教2022-08-08
關于Java異常處理的幾條建議_動力節(jié)點Java學院整理
Java提供了拋出異常、捕捉異常和finally語句的使用來處理程序異常,下面就來具體看一下關于Java異常處理的幾條建議2017-06-06

