Java中的定時(shí)器Timer詳解
簡(jiǎn)單來(lái)說(shuō),定時(shí)器就相當(dāng)于一個(gè)“鬧鐘”,給定時(shí)器設(shè)定一個(gè)任務(wù),約定這個(gè)任務(wù)在xxx時(shí)間之后執(zhí)行~
Timer類提供了一個(gè)核心接口,schedule(安排) 指定一個(gè)任務(wù)交給定時(shí)器,在一定時(shí)間之后再去執(zhí)行這個(gè)任務(wù)~
如何實(shí)現(xiàn)定時(shí)器的效果~
- Timer中要包含一個(gè)Task類,每個(gè)Task就表示一個(gè)具體的任務(wù)實(shí)例,Task里面包含一個(gè)時(shí)間戳(啥時(shí)候執(zhí)行這個(gè)任務(wù)),還包含一個(gè)Runnable實(shí)例(用來(lái)表示任務(wù)具體是啥)。
- Timer里面通過(guò)一個(gè)帶優(yōu)先級(jí)的阻塞隊(duì)列,來(lái)組織如干個(gè)task。
- 這里的優(yōu)先級(jí)是按照時(shí)間的先后來(lái)排優(yōu)先級(jí),快要到時(shí)間的任務(wù)優(yōu)先級(jí)更高。
- 也就是 給 堆 加上 wait/notify ,讓它能夠帶阻塞效果。
- 標(biāo)準(zhǔn)庫(kù)里提供這樣的隊(duì)列PriorityBlockingQueue
- Timer 中還需要一個(gè)專門的線程,讓這個(gè)線程不停的掃描隊(duì)首元素,看看隊(duì)首元素是不是已經(jīng)可以執(zhí)行了,如果可以執(zhí)行了,就執(zhí)行,反之繼續(xù)在隊(duì)列中等待。
具體如何用代碼實(shí)現(xiàn)這樣一個(gè)定時(shí)器Timer:
一般去設(shè)定時(shí)間的時(shí)候,傳入的時(shí)間,都是一個(gè)時(shí)間間隔
例如:傳入1000 ,就代表從當(dāng)前開(kāi)始過(guò)1000ms之后在執(zhí)行;
而我這里為了后面代碼方便判斷,在這里記錄一下絕對(duì)時(shí)間,這樣this.time里就是一個(gè)標(biāo)志的ms級(jí)時(shí)間戳了,后續(xù)只需要獲取當(dāng)前時(shí)間戳在和這里的time對(duì)比一下就好了。
Task 要放到一個(gè)優(yōu)先級(jí)隊(duì)列中,但是優(yōu)先級(jí)隊(duì)列里面是需要比較優(yōu)先級(jí)的,所以可以讓Task類實(shí)現(xiàn)Comparable接口,重寫compareTo方法來(lái)進(jìn)行比較。
這里希望時(shí)間較小的排在見(jiàn)面。
這里獲取一下當(dāng)前時(shí)間currTime,如果當(dāng)前時(shí)間大于等于task里約定的時(shí)間,超過(guò)說(shuō)明時(shí)間到,執(zhí)行任務(wù),反之沒(méi)到,把取去的
任務(wù)再放回隊(duì)列,繼續(xù)等待。
這里還涉及到一個(gè)問(wèn)題:
舉個(gè)例子:假如你早上定了一個(gè)8.30的 鬧鐘,8點(diǎn)的時(shí)候你醒了,看了下時(shí)間,還沒(méi)到,你就繼續(xù)睡,
但是這里是while(true),就意味著每過(guò)一秒鐘就要看一次鬧鐘,8.01看一次,8.02看一次,這樣就會(huì)白白的浪費(fèi)一些資源,這就出現(xiàn)了“盲等”,在等待任務(wù)時(shí)間的過(guò)程中,一直持有著CPU資源~
所以這里就需要優(yōu)化一下:使用wait/notify機(jī)制,就可很好的改善盲等問(wèn)題~
如果取出任務(wù)發(fā)現(xiàn)還沒(méi)到時(shí)間,就wait,等待一定時(shí)間,這里使用的wait()的重載版本,wait()里寫一個(gè)參數(shù),達(dá)到等待時(shí)間,自動(dòng)醒過(guò)來(lái)~ 此時(shí)就大大降低了掃描次數(shù)和成本,
這里的notify(),就是保證當(dāng)線程中如果有線程在WAITING狀態(tài)的線程,就需要顯示的喚醒一下線程。
舉個(gè)例子;
如果隊(duì)首元素8.30在執(zhí)行,等待30分鐘,但是此時(shí),可能突然插入一個(gè)任務(wù),讓你8.10的時(shí)候去干一件事,如果你8.30再去喚醒的話,8.10的任務(wù)就來(lái)不及了!
所以每次插入新任務(wù)的時(shí)候,都喚醒一下woeker線程,讓worker線程重新獲取一下隊(duì)首元素,看看接下來(lái)的任務(wù)等待多少時(shí)間合適。
//簡(jiǎn)單定時(shí)器 public class TestTimer { //每個(gè) Task 實(shí)例 就包含一個(gè)要執(zhí)行的任務(wù) //Task 要放到一個(gè)優(yōu)先級(jí)隊(duì)列中,但是優(yōu)先級(jí)隊(duì)列里面是需要比較優(yōu)先級(jí)的 static class Task implements Comparable<Task>{ //什么時(shí)候執(zhí)行 private long time; //執(zhí)行什么任務(wù) private Runnable command; public Task(Runnable command ,long time){ this.command = command; this.time = System.currentTimeMillis()+time; } public void run(){ //執(zhí)行Runable 里面的run方法 command.run(); } @Override public int compareTo(Task o) { return (int)(this.time - o.time); } } static class Timer{ //先創(chuàng)建一個(gè)帶優(yōu)先級(jí)的阻塞隊(duì)列 private PriorityBlockingQueue<Task> queue = new PriorityBlockingQueue<>(); //用這個(gè)對(duì)象來(lái)完成線程之間的協(xié)調(diào)任務(wù) private Object meilbox = new Object(); //schedule 方法就是把一個(gè)Task 放在Timer中 public void schedule(Runnable command,long after){ Task task = new Task(command,after); //將當(dāng)前任務(wù)放入對(duì)列 queue.put(task); //當(dāng)worker 線程中包含wait機(jī)制的時(shí)候,在安排任務(wù)的時(shí)候就需要顯示的喚醒一下 synchronized (meilbox){ meilbox.notify(); } } //寫一個(gè)構(gòu)造方法,創(chuàng)建線程 public Timer(){ //創(chuàng)建一個(gè)線程,讓這個(gè)線程去掃描隊(duì)列的隊(duì)首元素,看看能不能執(zhí)行 Thread worker = new Thread(){ @Override public void run() { //取出隊(duì)首元素,判斷這個(gè)元素能不能執(zhí)行 while(true){ try { Task task = queue.take(); long currTime = System.currentTimeMillis(); if(currTime >= task.time){ //時(shí)間到,執(zhí)行任務(wù) task.run(); }else{ //時(shí)間沒(méi)到,繼續(xù)等待 queue.put(task); synchronized (meilbox){ meilbox.wait(task.time-currTime); } } } catch (InterruptedException e) { e.printStackTrace(); } } } }; worker.start(); } } }
測(cè)試:
public static void main(String[] args) { Timer timer = new Timer(); Runnable command = new Runnable() { @Override public void run() { System.out.println("時(shí)間到了~"); // timer.schedule(this,3000); 每隔3是就執(zhí)行一次 } }; System.out.println("安排任務(wù)"); timer.schedule(command,3000); } }
安排任務(wù)后,等待3s就可以執(zhí)行了
這里補(bǔ)充一下Timer原生類中的一些方法
- schedule(TimerTask task, Date time) 在指定的日期執(zhí)行一次TimerTask任務(wù);如果日期time早于當(dāng)前時(shí)間,則立刻執(zhí)行。
- schedule(TimerTask task, long delay, long period) 以當(dāng)前時(shí)間為基準(zhǔn),延遲指定的毫秒后,再按指定的時(shí)間間隔地?zé)o限次數(shù)的執(zhí)行TimerTask任務(wù)。
- schedule(TimerTask task, Date firstTime, long period) 在指定的日期之后,按指定的時(shí)間間隔地?zé)o限次數(shù)的執(zhí)行TimerTask任務(wù)。
- scheduleAtFixedRate(TimerTask task, long delay, long period) 以當(dāng)前時(shí)間為基準(zhǔn),延遲指定的毫秒后,再按指定的時(shí)間間隔周期性地?zé)o限次數(shù)的執(zhí)行TimerTask任務(wù)。
總結(jié)
本篇文章就到這里了,希望能給你帶來(lái)幫助,也希望您能夠多多關(guān)注腳本之家的更多內(nèi)容!
- java當(dāng)中的定時(shí)器的4種使用方式
- Java定時(shí)任務(wù)的三種實(shí)現(xiàn)方法
- Java中指定時(shí)區(qū)的3種方法
- Java定時(shí)器Timer使用方法詳解
- Java定時(shí)任務(wù)的三種實(shí)現(xiàn)方式
- java定時(shí)調(diào)度器(Quartz)使用實(shí)例
- Java 定時(shí)器(Timer,TimerTask)詳解及實(shí)例代碼
- java定時(shí)任務(wù)Timer和TimerTask使用詳解
- Java實(shí)現(xiàn)定時(shí)任務(wù)最簡(jiǎn)單的3種方法
- RxJava2.x實(shí)現(xiàn)定時(shí)器的實(shí)例代碼
- java基于QuartzJobBean實(shí)現(xiàn)定時(shí)功能的示例代碼
相關(guān)文章
java實(shí)現(xiàn)導(dǎo)出數(shù)據(jù)為zip壓縮文件
這篇文章主要為大家詳細(xì)介紹了java如何實(shí)現(xiàn)導(dǎo)出數(shù)據(jù)為zip壓縮文件,并且解壓后為json文件,文中的示例代碼講解詳細(xì),感興趣的小伙伴可以跟隨小編一起學(xué)習(xí)一下2023-11-11SpringBoot+Redis實(shí)現(xiàn)查找附近用戶的示例代碼
SpringDataRedis提供了十分簡(jiǎn)單的地理位置定位的功能,本文主要介紹了SpringBoot+Redis實(shí)現(xiàn)查找附近用戶的示例代碼,具有一定的參考價(jià)值,感興趣的可以了解一下2024-02-02SpringMVC 使用JSR-303進(jìn)行校驗(yàn) @Valid示例
本篇文章主要介紹了SpringMVC 使用JSR-303進(jìn)行校驗(yàn) @Valid示例,小編覺(jué)得挺不錯(cuò)的,現(xiàn)在分享給大家,也給大家做個(gè)參考。一起跟隨小編過(guò)來(lái)看看吧2017-02-02解決SpringSecurity 一直登錄失敗的問(wèn)題
這篇文章主要介紹了解決SpringSecurity 一直登錄失敗的問(wèn)題,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2021-06-06spring基礎(chǔ)概念A(yù)OP與動(dòng)態(tài)代理理解
這篇文章主要為大家詳細(xì)介紹了spring基礎(chǔ)概念A(yù)OP與動(dòng)態(tài)代理,文中示例代碼介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2016-10-10