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




免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM