Java任務定時執行器案例的實現

⭐️前面的話⭐️

本篇文章將介紹Java多線程案例,定時器,定時器就像鬧鐘一樣,等到瞭指定的時間,鬧鐘就會發出響聲來提醒您,而定時器會執行指定的任務。

🍎1.定時器概述

🍏1.1認識定時器

java中的定時器,也可以叫做任務定時執行器,顧名思義,就是等到瞭指定的時間,就去做指定的事情,就像你每周六或周日都要去力扣參加周賽一樣。

所以你如果想要使用定時器,你需要交代時間和對應的任務才行,java標準也提供瞭帶有定時器功能的類Timer

🍏1.2Timer類的使用

在java1.8中,Timer給出瞭四個構造方法,這些構造方法可以去指定線程的名字和是否將定時器內部的線程指定為守護線程。

好瞭,又出現瞭一個新概念,這個守護線程是什麼鬼?
其實在java中有兩種線程,一種是用戶線程,另外一種是守護線程。用戶線程就是普通的線程,守護線程顧名思義就是守護用戶線程的線程,可以說就是用戶線程的保姆,守護線程與JVM“共存亡”, 隻要存在一個用戶線程,程序中所有的守護線程都不會停止工作,直到最後一個用戶線程執行完畢,守護線程才會停止工作。守護線程最典型的應用就是 GC (垃圾回收器),它就是一個非常稱職的守護者。

🍊構造方法:

序號 構造方法 說明
1 public Timer() 無參數構造方法,默認定時器關聯的線程不是守護線程,線程名字也是默認值
2 public Timer(boolean isDaemon) 指定定時器中關聯的線程是否為守護線程,如果是,參數為true
3 public Timer(String name) 指定定時器關聯線程名稱,線程類型默認為非守護線程
4 public Timer(String name, boolean isDaemon) 指定定時器關聯線程名和線程類型

Timer類構造時內部也會創建線程,如果不指定,定時器對象內部的線程(為瞭簡化,就稱為關聯線程吧)的類型是用戶線程,而不是守護線程。

🍊核心方法:

序號 方法 說明
1 public void schedule(TimerTask task, long delay) 指定任務,延遲多久執行該任務
2 public void schedule(TimerTask task, Date time) 指定任務,指定任務的執行時間
3 public void schedule(TimerTask task, long delay, long period) 連續執行指定任務,延遲時間,連續執行任務的時間間隔,毫秒為單位
4 public void schedule(TimerTask task, Date firstTime, long period) 連續執行指定任務,第一次任務的執行時間,連續執行任務的時間間隔
5 public void scheduleAtFixedRate(TimerTask task, Date firstTime, long period) 與方法4作用相同
6 public void scheduleAtFixedRate(TimerTask task, long delay, long period) 與方法3作用相同
7 public void cancel() 終止定時器所有任務,終止執行的任務不受影響

🍊使用演示:

import java.util.Timer;
import java.util.TimerTask;
import java.util.concurrent.PriorityBlockingQueue;

public class TimeProgram {
    public static void main(String[] args) throws InterruptedException {
        Timer timer = new Timer();
        timer.schedule(new TimerTask() {
            @Override
            public void run() {
                System.out.println("執行延後2s執行的任務!");
            }
        }, 2000);
        timer.schedule(new TimerTask() {
            @Override
            public void run() {
                System.out.println("執行延後5s執行的任務!");
            }
        }, 5000);

        //每秒輸出一個mian
        for (int i = 0; i < 5; i++) {
            System.out.println("main");
            Thread.sleep(1000);
        }
    }
}

🍊運行結果:

TimerTask類就是專門描述定時器任務的一個抽象類,它實現瞭Runnable接口。

public abstract class TimerTask implements Runnable    //jdk源碼

下面我們簡單實現一下定時器,我們就不用TimerTask瞭,我們直接使用Runnable,因為TimerTask實現瞭Runnable接口,所以後面測試我們自己所寫的schedule方法時,也可以傳入TimerTask類型的引用,既然是簡單地實現,那就不實現連續執行的功能瞭。

🍎2.定時器的簡單實現

首先,我們需要建造一個類,來描述定時器的任務,可以使用Runnable加上一個任務執行的時間戳就可以瞭。
🍊具體清單:
一個構造方法,用來指定任務和延遲執行時間。
兩個獲取方法,用來給外部對象獲取該對象的任務和執行時間。
實現比較器,用於定時器任務對象的組織,畢竟,每次需要執行時間最早的任務,需要用到基於小根堆實現的優先隊列,不,還需要考慮多線程的情況,還是使用優先級阻塞隊列吧。

//我的任務
class MyTask implements Comparable<MyTask> {
    //接收具體任務
    private Runnable runnable;
    //執行時的時間戳
    private long time;

    //構造方法
    public MyTask(Runnable runnable, int delay) {
        this.runnable = runnable;
        this.time = System.currentTimeMillis() + delay;
    }

    //執行任務
    public void run() {
        this.runnable.run();
    }
    //獲取執行時間
    public long getTime() {
        return this.time;
    }

    //實現comparable接口,方便創建優先級阻塞隊列
    @Override
    public int compareTo(MyTask o) {
        return (int) (this.time - o.time);
    }
}

接下來就要實現定時器類瞭,首先我們需要一個數據結構來組織定時器任務,並且每次都能將時間最早的任務找到並執行,那麼這個數據結構非小根堆莫屬瞭,也就是優先級隊列,註意對自定義類使用優先級隊列時,一定要實現比較器。

    //每次執行任務時,需要優先執行時間在前的任務,即每次執行任務要選擇時間戳最小的任務,在多線程情況中優先級阻塞隊列是最佳選擇
    private static final PriorityBlockingQueue<MyTask> priorityBlockingQueue = new PriorityBlockingQueue<>();

然後,需要一個方法將任務安排在優先級阻塞隊列中,最後在構造定時器對象的時候從優先級阻塞隊列中取任務並在指定的時間執行。

按照上圖的邏輯,我們自己實現的定時器類需要有一個線程專門去執行任務,執行任務過程中可能會遇到執行時間還沒有到的情況,那麼線程必須得等待,線程等待的方法有兩種,一種是wait另一種是sleep,這個案例我們推薦前者,因為sleep方法不能中途喚醒,這個案例是有可能需要中途喚醒的,那就是有新任務插入時,需要重新去優先級阻塞隊列拿任務重復上述操作,這個喚醒操作可以使用notify方法實現,所以需要用到wait/notify組合拳,既然需要使用wait/notify那麼就得有鎖,所以我們可以使用一個專門的鎖對象來加鎖。

🍊實現代碼:

//我的定時類 用來管理任務
class MyTimer {
    //專門對鎖對象
    private final Object locker = new Object();
    //每次執行任務時,需要優先執行時間在前的任務,即每次執行任務要選擇時間戳最小的任務,在多線程情況中優先級阻塞隊列是最佳選擇
    private static final PriorityBlockingQueue<MyTask> priorityBlockingQueue = new PriorityBlockingQueue<>();

    //安排任務
    public void schedule(Runnable runnable, int delay) {
        //將任務放入小根堆中
        MyTask task = new MyTask(runnable, delay);
        priorityBlockingQueue.put(task);
        //每次當新任務加載到阻塞隊列時,需要中途喚醒線程,因為新進來的任務可能是最早需要執行的
        synchronized (locker) {
            locker.notify();
        }
    }
    public MyTimer() {
        Thread thread = new Thread(() -> {
            while (true) {
                try {
                    //加載任務,確定執行時機
                    MyTask myTask = priorityBlockingQueue.take();
                    long curTime = System.currentTimeMillis();
                    //時間未到,將任務放回
                    if (curTime < myTask.getTime()) {
                        synchronized (locker) {
                            priorityBlockingQueue.put(myTask);
                            //放回任務後,不能立即就再次取該任務加載,需要設置一個再次加載的等待時間,建議使用wait帶參數的方法
                            //因為wait方法可以使用notify進行中途喚醒,而sleep不能中途喚醒
                            int delay = (int)(myTask.getTime() - curTime);
                            locker.wait(delay);
                        }
                    } else {
                        System.out.println(Thread.currentThread().getName() + "線程收到任務,正在執行中!");
                        myTask.run();
                        System.out.println(Thread.currentThread().getName() + "線程執行任務完畢,正在等待新任務!");
                    }
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        });
        //不要忘瞭啟動線程
        thread.start();
    }
}

🍊上面是我們實現定時器的代碼,我們來測試一下:

import java.util.TimerTask;
import java.util.concurrent.PriorityBlockingQueue;

public class TimeProgram {
    public static void main(String[] args) throws InterruptedException {
        MyTimer timer = new MyTimer();
        timer.schedule(new TimerTask() {
            @Override
            public void run() {
                System.out.println("執行延後2s執行的任務!");
            }
        }, 2000);
        timer.schedule(new TimerTask() {
            @Override
            public void run() {
                System.out.println("執行延後5s執行的任務!");
            }
        }, 5000);

        //每秒輸出一個mian
        for (int i = 0; i < 5; i++) {
            System.out.println("main");
            Thread.sleep(1000);
        }
    }
}

🍊執行結果:

好瞭,任務定時執行器你學會瞭嗎?

到此這篇關於Java任務定時執行器案例的實現的文章就介紹到這瞭,更多相關Java任務定時執行器內容請搜索WalkonNet以前的文章或繼續瀏覽下面的相關文章希望大傢以後多多支持WalkonNet!

推薦閱讀: