Java任務定時執(zhí)行器案例的實現(xiàn)
??前面的話??
本篇文章將介紹Java多線程案例,定時器,定時器就像鬧鐘一樣,等到了指定的時間,鬧鐘就會發(fā)出響聲來提醒您,而定時器會執(zhí)行指定的任務。
??1.定時器概述
??1.1認識定時器
java中的定時器,也可以叫做任務定時執(zhí)行器,顧名思義,就是等到了指定的時間,就去做指定的事情,就像你每周六或周日都要去力扣參加周賽一樣。
所以你如果想要使用定時器,你需要交代時間和對應的任務才行,java標準也提供了帶有定時器功能的類Timer。
??1.2Timer類的使用
在java1.8中,Timer給出了四個構(gòu)造方法,這些構(gòu)造方法可以去指定線程的名字和是否將定時器內(nèi)部的線程指定為守護線程。
好了,又出現(xiàn)了一個新概念,這個守護線程是什么鬼?
其實在java中有兩種線程,一種是用戶線程,另外一種是守護線程。用戶線程就是普通的線程,守護線程顧名思義就是守護用戶線程的線程,可以說就是用戶線程的保姆,守護線程與JVM“共存亡”, 只要存在一個用戶線程,程序中所有的守護線程都不會停止工作,直到最后一個用戶線程執(zhí)行完畢,守護線程才會停止工作。守護線程最典型的應用就是 GC (垃圾回收器),它就是一個非常稱職的守護者。
??構(gòu)造方法:
| 序號 | 構(gòu)造方法 | 說明 |
|---|---|---|
| 1 | public Timer() | 無參數(shù)構(gòu)造方法,默認定時器關(guān)聯(lián)的線程不是守護線程,線程名字也是默認值 |
| 2 | public Timer(boolean isDaemon) | 指定定時器中關(guān)聯(lián)的線程是否為守護線程,如果是,參數(shù)為true |
| 3 | public Timer(String name) | 指定定時器關(guān)聯(lián)線程名稱,線程類型默認為非守護線程 |
| 4 | public Timer(String name, boolean isDaemon) | 指定定時器關(guān)聯(lián)線程名和線程類型 |
Timer類構(gòu)造時內(nèi)部也會創(chuàng)建線程,如果不指定,定時器對象內(nèi)部的線程(為了簡化,就稱為關(guān)聯(lián)線程吧)的類型是用戶線程,而不是守護線程。
??核心方法:
| 序號 | 方法 | 說明 |
|---|---|---|
| 1 | public void schedule(TimerTask task, long delay) | 指定任務,延遲多久執(zhí)行該任務 |
| 2 | public void schedule(TimerTask task, Date time) | 指定任務,指定任務的執(zhí)行時間 |
| 3 | public void schedule(TimerTask task, long delay, long period) | 連續(xù)執(zhí)行指定任務,延遲時間,連續(xù)執(zhí)行任務的時間間隔,毫秒為單位 |
| 4 | public void schedule(TimerTask task, Date firstTime, long period) | 連續(xù)執(zhí)行指定任務,第一次任務的執(zhí)行時間,連續(xù)執(zhí)行任務的時間間隔 |
| 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() | 終止定時器所有任務,終止執(zhí)行的任務不受影響 |
??使用演示:
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("執(zhí)行延后2s執(zhí)行的任務!");
}
}, 2000);
timer.schedule(new TimerTask() {
@Override
public void run() {
System.out.println("執(zhí)行延后5s執(zhí)行的任務!");
}
}, 5000);
//每秒輸出一個mian
for (int i = 0; i < 5; i++) {
System.out.println("main");
Thread.sleep(1000);
}
}
}
??運行結(jié)果:

TimerTask類就是專門描述定時器任務的一個抽象類,它實現(xiàn)了Runnable接口。
public abstract class TimerTask implements Runnable //jdk源碼
下面我們簡單實現(xiàn)一下定時器,我們就不用TimerTask了,我們直接使用Runnable,因為TimerTask實現(xiàn)了Runnable接口,所以后面測試我們自己所寫的schedule方法時,也可以傳入TimerTask類型的引用,既然是簡單地實現(xiàn),那就不實現(xiàn)連續(xù)執(zhí)行的功能了。
??2.定時器的簡單實現(xiàn)
首先,我們需要建造一個類,來描述定時器的任務,可以使用Runnable加上一個任務執(zhí)行的時間戳就可以了。
??具體清單:
一個構(gòu)造方法,用來指定任務和延遲執(zhí)行時間。
兩個獲取方法,用來給外部對象獲取該對象的任務和執(zhí)行時間。
實現(xiàn)比較器,用于定時器任務對象的組織,畢竟,每次需要執(zhí)行時間最早的任務,需要用到基于小根堆實現(xiàn)的優(yōu)先隊列,不,還需要考慮多線程的情況,還是使用優(yōu)先級阻塞隊列吧。
//我的任務
class MyTask implements Comparable<MyTask> {
//接收具體任務
private Runnable runnable;
//執(zhí)行時的時間戳
private long time;
//構(gòu)造方法
public MyTask(Runnable runnable, int delay) {
this.runnable = runnable;
this.time = System.currentTimeMillis() + delay;
}
//執(zhí)行任務
public void run() {
this.runnable.run();
}
//獲取執(zhí)行時間
public long getTime() {
return this.time;
}
//實現(xiàn)comparable接口,方便創(chuàng)建優(yōu)先級阻塞隊列
@Override
public int compareTo(MyTask o) {
return (int) (this.time - o.time);
}
}
接下來就要實現(xiàn)定時器類了,首先我們需要一個數(shù)據(jù)結(jié)構(gòu)來組織定時器任務,并且每次都能將時間最早的任務找到并執(zhí)行,那么這個數(shù)據(jù)結(jié)構(gòu)非小根堆莫屬了,也就是優(yōu)先級隊列,注意對自定義類使用優(yōu)先級隊列時,一定要實現(xiàn)比較器。
//每次執(zhí)行任務時,需要優(yōu)先執(zhí)行時間在前的任務,即每次執(zhí)行任務要選擇時間戳最小的任務,在多線程情況中優(yōu)先級阻塞隊列是最佳選擇
private static final PriorityBlockingQueue<MyTask> priorityBlockingQueue = new PriorityBlockingQueue<>();
然后,需要一個方法將任務安排在優(yōu)先級阻塞隊列中,最后在構(gòu)造定時器對象的時候從優(yōu)先級阻塞隊列中取任務并在指定的時間執(zhí)行。

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

??實現(xiàn)代碼:
//我的定時類 用來管理任務
class MyTimer {
//專門對鎖對象
private final Object locker = new Object();
//每次執(zhí)行任務時,需要優(yōu)先執(zhí)行時間在前的任務,即每次執(zhí)行任務要選擇時間戳最小的任務,在多線程情況中優(yōu)先級阻塞隊列是最佳選擇
private static final PriorityBlockingQueue<MyTask> priorityBlockingQueue = new PriorityBlockingQueue<>();
//安排任務
public void schedule(Runnable runnable, int delay) {
//將任務放入小根堆中
MyTask task = new MyTask(runnable, delay);
priorityBlockingQueue.put(task);
//每次當新任務加載到阻塞隊列時,需要中途喚醒線程,因為新進來的任務可能是最早需要執(zhí)行的
synchronized (locker) {
locker.notify();
}
}
public MyTimer() {
Thread thread = new Thread(() -> {
while (true) {
try {
//加載任務,確定執(zhí)行時機
MyTask myTask = priorityBlockingQueue.take();
long curTime = System.currentTimeMillis();
//時間未到,將任務放回
if (curTime < myTask.getTime()) {
synchronized (locker) {
priorityBlockingQueue.put(myTask);
//放回任務后,不能立即就再次取該任務加載,需要設(shè)置一個再次加載的等待時間,建議使用wait帶參數(shù)的方法
//因為wait方法可以使用notify進行中途喚醒,而sleep不能中途喚醒
int delay = (int)(myTask.getTime() - curTime);
locker.wait(delay);
}
} else {
System.out.println(Thread.currentThread().getName() + "線程收到任務,正在執(zhí)行中!");
myTask.run();
System.out.println(Thread.currentThread().getName() + "線程執(zhí)行任務完畢,正在等待新任務!");
}
} catch (InterruptedException e) {
e.printStackTrace();
}
}
});
//不要忘了啟動線程
thread.start();
}
}
??上面是我們實現(xiàn)定時器的代碼,我們來測試一下:
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("執(zhí)行延后2s執(zhí)行的任務!");
}
}, 2000);
timer.schedule(new TimerTask() {
@Override
public void run() {
System.out.println("執(zhí)行延后5s執(zhí)行的任務!");
}
}, 5000);
//每秒輸出一個mian
for (int i = 0; i < 5; i++) {
System.out.println("main");
Thread.sleep(1000);
}
}
}
??執(zhí)行結(jié)果:

好了,任務定時執(zhí)行器你學會了嗎?
到此這篇關(guān)于Java任務定時執(zhí)行器案例的實現(xiàn)的文章就介紹到這了,更多相關(guān)Java任務定時執(zhí)行器內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
Java兩種常用的隨機數(shù)生成方式(小白總結(jié))
這篇文章主要介紹了Java兩種常用的隨機數(shù)生成方式(小白總結(jié)),文中通過示例代碼介紹的非常詳細,對大家的學習或者工作具有一定的參考學習價值,需要的朋友們下面隨著小編來一起學習學習吧2020-10-10
SpringBoot如何使用applicationContext.xml配置文件
這篇文章主要介紹了SpringBoot使用applicationContext.xml配置文件,具有很好的參考價值,希望對大家有所幫助。如有錯誤或未考慮完全的地方,望不吝賜教2022-06-06
Spring Boot監(jiān)聽Redis Key失效事件實現(xiàn)定時任務的示例
這篇文章主要介紹了Spring Boot監(jiān)聽Redis Key失效事件實現(xiàn)定時任務的示例,文中通過示例代碼介紹的非常詳細,對大家的學習或者工作具有一定的參考學習價值,需要的朋友們下面隨著小編來一起學習學習吧2020-04-04

