Java多線程案例之定時器詳解
一.什么是定時器
定時器也是軟件開發(fā)中的一個重要組件. 類似于一個 “鬧鐘”. 達到一個設定的時間之后, 就執(zhí)行某個指定好的代碼
定時器是一種實際開發(fā)中非常常用的組件,我們舉幾個例子:
1.比如網(wǎng)絡通信中, 如果對方 500ms 內(nèi)沒有返回數(shù)據(jù), 則斷開連接嘗試重連
2.比如一個 Map, 希望里面的某個 key 在 3s 之后過期(自動刪除)
以上類似于這樣的場景就需要用到定時器
二.標準庫中的定時器(timer)
2.1什么是定時器
標準庫中供了一個 Timer 類. Timer 類的核心方法為 schedule ,schedule 包含兩個參數(shù). 第一個參數(shù)指定即將要執(zhí)行的任務代碼TimerTask, 第二個參數(shù)指定多長時間之后執(zhí)行 (單位為毫秒).
Timer timer = new Timer(); timer.schedule(new TimerTask() { @Override public void run() { System.out.println("hello"); } }, 3000);
2.2定時器的使用
Timer的構(gòu)造方法
構(gòu)造方法 | 說明 |
---|---|
public Timer() | 無參數(shù)構(gòu)造方法,默認定時器關聯(lián)的線程不是守護線程,線程名字也是默認值 |
public Timer(boolean isDaemon) | 指定定時器中關聯(lián)的線程是否為守護線程,如果是,參數(shù)為true |
public Timer(String name) | 指定定時器關聯(lián)線程名稱,線程類型默認為非守護線程 |
public Timer(String name, boolean isDaemon) | 指定定時器關聯(lián)線程名和線程類型 |
Timer方法
方法 | 說明 |
---|---|
public void schedule (TimerTask task, long delay) | 指定任務,延遲多久執(zhí)行該任務 |
public void schedule(TimerTask task, Date time) | 指定任務,指定任務的執(zhí)行時間 |
public void schedule(TimerTask task, long delay, long period) | 連續(xù)執(zhí)行指定任務,延遲時間,連續(xù)執(zhí)行任務的時間間隔,毫秒為單位 |
public void schedule(TimerTask task, Date firstTime, long period) | 連續(xù)執(zhí)行指定任務,第一次任務的執(zhí)行時間,連續(xù)執(zhí)行任務的時間間隔 |
public void scheduleAtFixedRate(TimerTask task, Date firstTime, long period) | 連續(xù)執(zhí)行指定任務,第一次任務的執(zhí)行時間,連續(xù)執(zhí)行任務的時間間隔 |
public void scheduleAtFixedRate(TimerTask task, long delay, long period) | 連續(xù)執(zhí)行指定任務,延遲時間,連續(xù)執(zhí)行任務的時間間隔,毫秒為單位 |
public void cancel() | 終止定時器所有任務,終止執(zhí)行的任務不受影響 |
TimerTask是專門來實現(xiàn)Runnable接口的
下面我們會實現(xiàn)一下定時器,我們就不用TimerTask了,我們直接使用Runnable,因為TimerTask實現(xiàn)了Runnable接口,所以后面測試我們自己所寫的schedule方法時,也可以傳入TimerTask類型的引用,既然是簡單地實現(xiàn),那就不實現(xiàn)連續(xù)執(zhí)行的功能了。.
public class Test { public static void main(String[] args){ Timer timer = new Timer(); timer.schedule(new TimerTask() { @Override public void run() { System.out.println("執(zhí)行線程在5s后執(zhí)行"); } },5000); 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í)行線程在3s后執(zhí)行"); } },3000); } }
三.實現(xiàn)定時器
3.1什么是定時器
定時器的構(gòu)成:一個帶優(yōu)先級的阻塞隊列
為啥要帶優(yōu)先級呢?
因為阻塞隊列中的任務都有各自的執(zhí)行時刻 (delay). 最先執(zhí)行的任務一定是 delay 最小的. 使用帶優(yōu)先級的隊列就可以高效的把這個 delay 最小的任務找出來.
1.隊列中的每個元素是一個 Task 對象,Task 中帶有一個時間屬性, 隊首元素就是即將同時有一個 worker 線程一直掃描隊首元素, 看隊首元素是否需要執(zhí)行
class MyTask implements Comparable<MyTask>{ //執(zhí)行的時間戳 private long time; //接受具體任務 private Runnable runnable; //創(chuàng)建MyTask構(gòu)造方法 public MyTask(Runnable runnable,long time) { //通過currentTimeMillis來獲取time 中存的是絕對時間, 超過這個時間的任務就應該被執(zhí)行 this.time = System.currentTimeMillis()+time; this.runnable = runnable; } //執(zhí)行任務 public void run(){ this.runnable.run(); } //提供對外time public long getTime() { return time; } //執(zhí)行comparable接口來進行時間的比較,并將time的long類型轉(zhuǎn)換為int類型 @Override public int compareTo(MyTask o) { return (int)(this.time-o.time); } }
Timer 實例中, 通過 PriorityBlockingQueue 來組織若干個 Task 對象.通過 schedule 來往隊列中插入一個個 Task 對象.
class MyTimer{ // 定時器內(nèi)部要能夠存放多個任務 private PriorityBlockingQueue<MyTask> queue = new PriorityBlockingQueue<>(); //為鎖創(chuàng)建一個對象 Object locker = new Object(); public void schedule(Runnable runnable, long delay) { MyTask task = new MyTask(runnable, delay); queue.put(task); // 每次任務插入成功之后, 都喚醒一下掃描線程, 讓線程重新檢查一下隊首的任務看是否時間到要執(zhí)行~~ synchronized (locker) { locker.notify(); } }
Timer 類中存在一個 worker 線程, 一直不停的掃描隊首元素, 看看是否能執(zhí)行這個任務.所謂 “能執(zhí)行” 指的是該任務設定的時間已經(jīng)到達了
class MyTimer{ // 定時器內(nèi)部要能夠存放多個任務 private PriorityBlockingQueue<MyTask> queue = new PriorityBlockingQueue<>(); //為鎖創(chuàng)建一個對象 Object locker = new Object(); public void schedule(Runnable runnable, long delay) { MyTask task = new MyTask(runnable, delay); queue.put(task); // 每次任務插入成功之后, 都喚醒一下掃描線程, 讓線程重新檢查一下隊首的任務看是否時間到要執(zhí)行~~ synchronized (locker) { locker.notify(); } } public MyTimer() { Thread t = new Thread(() -> { while (true) { try { // 先取出隊首元素 MyTask task = queue.take(); // 再比較一下看看當前這個任務時間到了沒? long curTime = System.currentTimeMillis(); if (curTime < task.getTime()) { // 時間沒到, 把任務再塞回到隊列中. queue.put(task); // 指定一個等待時間,防止有的線程需要等待時間很長,但是線程一直運行等待時間到來執(zhí)行,這樣會占有CPU占有資源 synchronized (locker) { locker.wait(task.getTime() - curTime); } } else { // 時間到了, 執(zhí)行這個任務 task.run(); } } catch (InterruptedException e) { e.printStackTrace(); } } }); t.start(); } }
3.2最終實現(xiàn)代碼
package thread; import java.util.PriorityQueue; import java.util.concurrent.PriorityBlockingQueue; // 創(chuàng)建一個類, 表示一個任務. class MyTask implements Comparable<MyTask> { // 任務具體要干啥 private Runnable runnable; // 任務具體啥時候干. 保存任務要執(zhí)行的毫秒級時間戳 private long time; // after 是一個時間間隔. 不是絕對的時間戳的值 public MyTask(Runnable runnable, long delay) { this.runnable = runnable; this.time = System.currentTimeMillis() + delay; } public void run() { runnable.run(); } public long getTime() { return time; } @Override public int compareTo(MyTask o) { // 到底是誰見誰, 才是一個時間小的在前? 需要咱們背下來. return (int) (this.time - o.time); } } class MyTimer { // 定時器內(nèi)部要能夠存放多個任務 private PriorityBlockingQueue<MyTask> queue = new PriorityBlockingQueue<>(); public void schedule(Runnable runnable, long delay) { MyTask task = new MyTask(runnable, delay); queue.put(task); // 每次任務插入成功之后, 都喚醒一下掃描線程, 讓線程重新檢查一下隊首的任務看是否時間到要執(zhí)行~~ synchronized (locker) { locker.notify(); } } private Object locker = new Object(); public MyTimer() { Thread t = new Thread(() -> { while (true) { try { // 先取出隊首元素 MyTask task = queue.take(); // 再比較一下看看當前這個任務時間到了沒? long curTime = System.currentTimeMillis(); if (curTime < task.getTime()) { // 時間沒到, 把任務再塞回到隊列中. queue.put(task); // 指定一個等待時間 synchronized (locker) { locker.wait(task.getTime() - curTime); } } else { // 時間到了, 執(zhí)行這個任務 task.run(); } } catch (InterruptedException e) { e.printStackTrace(); } } }); t.start(); } } public class Test { public static void main(String[] args) { MyTimer myTimer = new MyTimer(); myTimer.schedule(new Runnable() { @Override public void run() { System.out.println("hello timer!"); } }, 3000); System.out.println("main"); } }
以上就是Java多線程案例之定時器詳解的詳細內(nèi)容,更多關于Java多線程 定時器的資料請關注腳本之家其它相關文章!
相關文章
詳解spring boot容器加載完后執(zhí)行特定操作
這篇文章主要介紹了詳解spring boot容器加載完后執(zhí)行特定操作,小編覺得挺不錯的,現(xiàn)在分享給大家,也給大家做個參考。一起跟隨小編過來看看吧2018-01-01