任務調度實現

任務調度

定義:任務調度是基於給定時間點,給定時間間隔或者給定執行次數自動執行任務。java

Timer

Timer 是jdk提供的一個定時器工具,會在主線程以外起一個單獨線程執行指定的計劃任務。數組

public class TimerTest extends TimerTask {

    private String jobName;

    public TimerTest(String jobName) {
        this.jobName = jobName;
    }

    @Override
    public void run() {
        System.out.println("執行:" + jobName);
    }

    public static void main(String[] args) {
        Timer timer = new Timer();
        long delay1 = 1 * 1000;
        long interval1 = 1000;

        //從如今開始1秒後每隔1秒執行一次job1
        timer.schedule(new TimerTest("job1"), delay1, interval1);

        long delay2 = 2 * 1000;
        long interval2 = 2000;
        // 從如今開始2秒鐘以後,每隔2秒鐘執行一次 job2
        timer.schedule(new TimerTest("job2"), delay2, interval2);

        try {
            Thread.sleep(10000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        //終止執行
        timer.cancel();
    }
}

Timer 的優勢在於簡單易用,但因爲全部任務都是由同一個線程來調度,所以全部任務都是串行執行的,同一時間只能有一個任務在執行,前一個任務的延遲或異常都將會影響到以後的任務併發

ScheduledExecutor

ScheduledExecutor 類則在自定義線程池的基礎上增長了週期性執行任務的功能ide

ScheduledThreadPoolExecutor 線程池中的每個線程負責執行一個定時任務,相互之間互不干擾,併發執行,線程池會輪詢任務狀態,執行時間到來纔會真正執行線程。函數

public class ScheduledExecutorTest implements Runnable {

    private String jobName;

    public ScheduledExecutorTest(String jobName) {
        this.jobName = jobName;
    }

    public void run() {
        System.out.println("Start: " + getDate());
        try {
            Thread.sleep(1000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println("execute " + jobName);
        System.out.println("End: " + getDate());
    }
    
    //獲取當前時間
    public String getDate() {
        SimpleDateFormat df = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");//設置日期格式
        return df.format(new Date());// new Date()爲獲取當前系統時間
    }

    public static void main(String[] args) {
        ScheduledExecutorService service = Executors.newScheduledThreadPool(10);

        long initialDelay1 = 1;
        long period1 = 2;
        //固定時間間隔
        service.scheduleAtFixedRate(new ScheduledExecutorTest("job1"),
                initialDelay1, period1, TimeUnit.SECONDS);

        long initialDelay2 = 1;
        long peroid2 = 3;
        //本次執行結束和下次開始執行時間保持在三秒
        //每次執行時間:executeTime + peroid executeTime可能會變化,可是peroid不變
        /*service.scheduleWithFixedDelay(new ScheduledExecutorTest("job2"), initialDelay2, peroid2, TimeUnit.SECONDS); */
    }
}

使用Executors.newScheduledThreadPool(10)建立一個核心線程數量爲10的線程池。工具

方法區別:
1.scheduleAtFixedRate(Runnable command, long initialDelay, long period, TimeUnit unit)
參數:this

  • Runnable 須要執行的task
  • initialDelay 延遲至啓動任務的時間
  • period 本次開始到下次開始的時間間隔

注:spa

  • 若本次job執行時間小於period,本次開始執行和下次開始之間時間間隔固定
  • 若本次job執行時間大於period,則本次開始時間和下次開始時間間隔爲job執行時間
  • 每次執行時間 :executeTime < period ? peroid : executeTime
  • 不會由於上一個線程的調度失效延遲而受到影響

2.scheduleWithFixedDelay(Runnable command, long initialDelay, long period, TimeUnit unit)
注:線程

  • 本次執行結束和下次開始執行時間保持在三秒
  • 每次執行時間:executeTime + peroid executeTime可能會變化,可是peroid不變
  • 受到上一個線程調度影響,可能會推遲本次任務調度的執行

實現原理:

ScheduledThreadPool建立的線程池底層使用的是DelayQueuecode

Delayed元素的一個無界阻塞隊列,只有在延遲期滿時才能從中提取元素。該隊列的頭部是延遲期滿後保存時間最長的Delayed元素。若是延遲都尚未期滿,則隊列沒有頭部,而且poll將返回null。

當一個元素的 getDelay(TimeUnit.NANOSECONDS)方法返回一個小於等於0的值時,將發生到期。即便沒法使用take或poll移除未到期的元素,也不會將這些元素做爲正常元素對待。例如,size方法同時返回到期和未到期元素的計數。此隊列不容許使用null元素

ScheduledThreadPoolExecutor是把任務添加到DelayedWorkQueue中,而DelayedWorkQueue則是相似於DelayQueue,內部維護着一個以時間爲前後順序的優先級隊列,使用compareTo()方法使用與DelayedWorkQueue隊列對其元素ScheduledThreadPoolExecutor task進行排序

小結:

上面建立線程池建議使用ScheduledThreadPoolExecutor,能夠更加精確控制。 構造函數其中一個參數爲DelayedWorkQueue,內部經過一個RunnableScheduledFuture數組保存Runnable任務,默認初始長度爲16,它是一個優先級隊列 它根據這個任務的下次執行時間來比較大小,這樣可以保證queue[0]位置上的元素是最近須要執行的任務。

相關文章
相關標籤/搜索