摘自:https://www.cnblogs.com/Howlet/p/15580411.html
Java 定時任務
還沒真正的遇到使用定時任務的場景,不管怎么說先學起來
1. 定時任務
很多情況下任務並非需要立即執行,而是需要往后或定期執行,這不可能人工去操作,所以定時任務就出現了。項目中肯定會用到使用定時任務的情況,筆者就需要定時去拉取埋點數據
使用定時任務的情況:
- 每周末凌晨備份數據
- 觸發條件 5 分鍾后發送郵件通知
- 30 分鍾未支付取消訂單
- 每 1 小時去拉取數據
- ......
2. Thread實現
筆試中首次遇到定時任務急急忙忙想出來的方法
2.1 使用
public class ThreadSchedule { public static void main(String[] args) { // 5 秒后執行任務 int interval = 1000 * 5; // 新線程執行 new Thread(() -> { try { Thread.sleep(interval); System.out.println("執行定時任務"); } catch (InterruptedException e) { e.printStackTrace(); } }).start(); } }
2.2 分析
- 定時不准確,因依賴底層硬件,Windows誤差為10微妙
- System.currentTimeMillis() 依賴系統硬件,還會受網絡時間同步修改
- System.nanoTime() 依賴 JVM 的運行納秒數,並不受同步影響,適用於計算准確的時間差
- 但計算當前日期還是要使用 currentTimeMillis 的格林威治時間,而 nanoTime 計算 JVM 運行時間不准確
3. java.util.Timer
Timer 負責執行計划功能,會啟動一個后台線程,而 TimerTask 負責任務邏輯
3.1 使用
public class TimeSchedule { public static void main(String[] args) { // 啟動一個守護線程 Timer timer = new Timer(); // 定時任務 TimerTask timerTask = new TimerTask() { @Override public void run() { System.out.println("執行定時任務"); } }; long delay = 0; // 延遲執行 long period = 1000 * 5; // 間隔 timer.scheduleAtFixedRate(timerTask, 1, period); // timer.cancel(); 可取消 } }
3.2 分析
// new Timer() 最終的構造函數會啟動一個線程 public Timer(String name) { thread.setName(name); thread.start(); } // 這個線程里面封裝了一個 Queue 優先級隊列,該線程會去隊列里不停執行里面的任務 class TimerThread extends Thread { private TaskQueue queue; TimerThread(TaskQueue queue) { this.queue = queue; } } // 這個隊列里面存放了各種 TimerTask 定時的任務邏輯 class TaskQueue { private TimerTask[] queue = new TimerTask[128]; }
- 只有一個單線程執行,所以是串行執行
- 某個任務執行時間較長會阻塞后面預定執行的任務,所以時間並不准確
- 線程報錯后續的定時任務直接停止
4. ScheduledExecutorService
java.util.concurrent中的工具類,是一個多線程的定時器
4.1 使用
public class ExecutorSchedule { public static void main(String[] args) { // 定時任務 Runnable runnable = new Runnable() { @Override public void run() { System.out.println("執行定時任務"); } }; // 線程池執行 long delay = 0; // 延遲執行 long period = 1000 * 5; // 間隔 ScheduledExecutorService service = Executors.newSingleThreadScheduledExecutor(); service.scheduleAtFixedRate(runnable, delay, period, TimeUnit.MILLISECONDS); }
筆者最常用的一個定時操作了,之前還寫過定時的探測任務
5. Spring的定時任務
需要開啟定時功能@EnableScheduling
@Component public class SpringSchedule { // cron 表達式,每秒執行一次 @Scheduled(cron = "*/1 * * * * ?") public void springSchedule(){ System.out.println("執行定時任務"); } }
底層是 ScheduledThreadPoolExecutor 線程池,和上面的 ScheduledExecutorService 是同根同源
6. XXL-JOB
xxl-job 是個人維護的分布式任務調度框架(國人寫的,有詳細的中文文檔),分為 調度中心 和 執行器。執行器就是定時任務,而調度中心則負責管理調用這些定時任務,調度中心也可以存儲定時任務通過腳本形式(Java 是 Grovvy)免編譯地實時下發到各服務中執行。最重要的是有 UI 界面,用戶友好的體驗
6.1 建立數據庫
xxl-job 的存儲是基於數據庫的,相對比 quartz 可保存在內存和數據庫有一點性能影響。首先第一步就是要建庫,在 xxl-job 官網有 SQL 語句 tables_xxl_job.sql,直接執行即可建庫建表
6.2 部署 xxl-job-admin 調度中心
從 Git 上拉取最新的代碼,然后編譯根模塊,填好 admin 模塊的數據庫地址等,即可啟動這個調度中心(支持 Docker 部署,更加方便)
6.3 創建定時任務
在需要定時任務的服務中 引入依賴、添加配置、創建定時任務
6.3.1 依賴
<!-- xxl-job-core --> <dependency> <groupId>com.xuxueli</groupId> <artifactId>xxl-job-core</artifactId> <version>${project.parent.version}</version> </dependency>
6.3.2 基本配置
# xxl-job admin address xxl.job.admin.addresses=http://xxx.xxx.xxx:8080/xxl-job-admin # xxl-job executor appname xxl.job.executor.appname=xxl-job-executor-demo
6.3.3 定時任務
@Component public class MyJob { @XxlJob("MyJob") public void MyJob() throws Exception { // 執行器日志記錄 XxlJobHelper.log("myjob is execute"); // 定時任務邏輯 System.out.println("myjob is executing"); // default success } }
6.4 執行定時任務
進入調度中心新建一個任務,然后執行定時任務即可(使用的是 RPC 遠程過程調用)
6.5 遇到的問題
默認執行器是自動注冊到調度中心的,但是時常進去的地址有問題而導致執行失敗,所以要手動錄入執行器的地址
6.6 分析
作為輕量級的分布式定時任務,有 UI 界面簡單方便使用,而且對代碼沒什么侵入性,已經能滿足大部分項目的需求了,筆者如果要用定時任務也會首選 xxl-job