Java中定時器java.util.Timer的簡單模擬
1.定時器
1.1 含義
在Java中,定時器(Timer)是一個工具類,用于安排任務(wù)(Task)在指定時間后執(zhí)行或以指定的時間間隔重復(fù)執(zhí)行。它可以用于執(zhí)行定時任務(wù)、定時調(diào)度和時間延遲等操作。
定時器(Timer)可以應(yīng)用于許多場景,比如:
- 調(diào)度任務(wù):當(dāng)你需要按照預(yù)定時間執(zhí)行任務(wù)時,可以使用定時器。例如,每天凌晨執(zhí)行數(shù)據(jù)備份、定時生成報表、定時發(fā)送通知等。
- 超時處理:當(dāng)你需要處理某個操作的超時情況時,可以使用定時器。例如,設(shè)置一個操作的超時時間,如果在規(guī)定時間內(nèi)未完成,則執(zhí)行相應(yīng)的超時處理邏輯。
1.2 標(biāo)準(zhǔn)庫中的定時器
Java中的定時器:java.util.Timer,它的常用方法:
| 方法 | 描述 |
|---|---|
| schedule(TimerTask task, Date time) | 安排在指定時間執(zhí)行任務(wù) |
| schedule(TimerTask task, long delay) | 安排在指定延遲時間后執(zhí)行任務(wù) |
| schedule(TimerTask task, long delay, long period) | 安排在指定延遲時間后以指定的時間間隔重復(fù)執(zhí)行任務(wù) |
| scheduleAtFixedRate(TimerTask task, Date firstTime, long period) | 安排在指定時間開始以固定的時間間隔重復(fù)執(zhí)行任務(wù) |
| scheduleAtFixedRate(TimerTask task, long delay, long period) | 安排在指定延遲時間后以固定的時間間隔重復(fù)執(zhí)行任務(wù) |
| cancel() | 取消定時器的所有任務(wù) |
| purge() | 從定時器的任務(wù)隊列中刪除所有已取消的任務(wù) |
public class Main {
public static void main(String[] args) {
Timer timer = new Timer();
//調(diào)度指定的任務(wù)在指定的延遲時間(3000ms)后執(zhí)行。
timer.schedule(new TimerTask() {
//待執(zhí)行的任務(wù)
@Override
public void run() {
System.out.println("hello");
}
},3000);
}
}也可以一次注冊多個任務(wù):
public class Main {
public static void main(String[] args) {
Timer timer = new Timer();
//在指定的延遲時間(1000ms)后執(zhí)行。
timer.schedule(new TimerTask() {
//待執(zhí)行的任務(wù)
@Override
public void run() {
System.out.println("任務(wù)1");
}
},1000);
//在指定的延遲時間(2000ms)后執(zhí)行。
timer.schedule(new TimerTask() {
//待執(zhí)行的任務(wù)
@Override
public void run() {
System.out.println("任務(wù)2");
}
},2000);
//在指定的延遲時間(3000ms)后執(zhí)行。
timer.schedule(new TimerTask() {
//待執(zhí)行的任務(wù)
@Override
public void run() {
System.out.println("任務(wù)3");
}
},3000);
}
}2.簡單模擬實現(xiàn)定時器
2.1 實現(xiàn)思路
1.使用一個數(shù)據(jù)結(jié)構(gòu)來保存所有的任務(wù),這些任務(wù)是根據(jù)時間的大小來進行先后執(zhí)行的,所以這里使用優(yōu)先級隊列。由于這里是多線程的環(huán)境,所以這里采用PriorityBlockingQueue(優(yōu)先級阻塞隊列),時間越小優(yōu)先級越高。
2.我們需要使用一個線程來掃描定時器里面的任務(wù)是否到達執(zhí)行時間,由于我們采用的是優(yōu)先級隊列數(shù)據(jù)結(jié)構(gòu),所以只需掃描隊首元素。如果隊首還沒到執(zhí)行時間,那么后面的元素不可能到達執(zhí)行時間。
3.任務(wù)用一個類MyTask來表示,這里需要實現(xiàn)Comparable接口,因為它需要存入優(yōu)先級隊列。其中的屬性:
//表示定時器中的任務(wù)
class MyTask implements Comparable<MyTask>{
//要執(zhí)行的任務(wù)內(nèi)容
private Runnable runnable;
//延遲時間
private long time;
public MyTask(Runnable runnable, long time) {
this.runnable = runnable;
this.time = time;
}
//為了便于后面的比較,需要提供 get 方法
public long getTime() {
return time;
}
//表示任務(wù)開始執(zhí)行
public void run(){
this.runnable.run();
}
@Override
public int compareTo(MyTask o) {
return (int)(this.getTime() - o.getTime());
}
}4.實現(xiàn)添加任務(wù)的方法schedule:
public class MyTimer {
//掃描線程
private Thread thread;
//優(yōu)先級隊列(這里為阻塞隊列)
private PriorityBlockingQueue<MyTask> queue = new PriorityBlockingQueue<>();
/**
* 這個方法是用來注冊(添加)任務(wù)的
* @param runnable 表示待執(zhí)行的任務(wù)
* @param after 表示多少時間過后執(zhí)行任務(wù)
*/
public void schedule(Runnable runnable,long after){
//添加任務(wù),注意這里的時間是 System.currentTimeMillis() + after
MyTask task = new MyTask(runnable,System.currentTimeMillis() + after);
queue.put(task);
}
}5.添加一個線程來檢測隊首元素:
//當(dāng)創(chuàng)建對象的時候就直接開啟一個線程
public MyTimer(){
thread = new Thread(()->{
while(true){
//取出隊首,如果到時間了就執(zhí)行。
try {
MyTask myTask = queue.take();
long curTime = System.currentTimeMillis();
if(curTime < myTask.getTime()){
//時間未到,不執(zhí)行
queue.put(myTask);
}else {
//時間已到,執(zhí)行
myTask.run();
}
} catch (InterruptedException e) {
e.printStackTrace();
}
}
});
thread.start();
}就這樣就完了嗎?其實不然,在上面代碼中while (true)轉(zhuǎn)的太快了, 造成了無意義的 CPU 浪費,如果第一個任務(wù)設(shè)定的是 1 min 之后執(zhí)行某個邏輯,那么在這一分鐘內(nèi) CPU 會一直存取隊首元素。所以這里需要借助該對象的wait / notify來解決 while (true) 的忙等問題。
public MyTimer(){
thread = new Thread(()->{
while(true){
//取出隊首,如果到時間了就執(zhí)行。
try {
MyTask myTask = queue.take();
long curTime = System.currentTimeMillis();
if(curTime < myTask.getTime()){
queue.put(myTask);
//時間未到,不執(zhí)行,這里的 this 表示 MyTimer 對象
synchronized (this){
//阻塞一段時間
this.wait(myTask.getTime() - curTime);
}
}else {
//時間已到,執(zhí)行
myTask.run();
}
} catch (InterruptedException e) {
e.printStackTrace();
}
}
});
thread.start();
}
/**
* 這個方法是用來注冊(添加)任務(wù)的
* @param runnable 表示待執(zhí)行的任務(wù)
* @param after 表示多少時間過后執(zhí)行任務(wù)
*/
public void schedule(Runnable runnable,long after){
//添加任務(wù),注意這里的時間是 System.currentTimeMillis() + after
MyTask task = new MyTask(runnable,System.currentTimeMillis() + after);
queue.put(task);
synchronized(this){
this.notify();
}
}修改 Timer 的 schedule 方法,每次有新任務(wù)到來的時候喚醒一下線程。(因為新插入的任務(wù)可能是需要馬上執(zhí)行的)。
還沒結(jié)束!上面的代碼還是有缺陷的。假設(shè)當(dāng) thread 線程執(zhí)行完 queue.take() 過后,myTask.getTime() - curTime 的值為 1 個小時。這時 CPU 調(diào)度了其它線程(假設(shè)為 t2) 執(zhí)行, t2 線程調(diào)用 schedule 方法,延時時間為 30 分鐘,并調(diào)用 put 方法,隨后再執(zhí)行 notify 方法。然而這時 wait 方法還沒有執(zhí)行,notify 相當(dāng)于失效了。這時CPU再調(diào)度 thread 線程執(zhí)行,但是 myTask.getTime() - curTime 的值本應(yīng)是 30 分鐘(新添加了一個任務(wù)),但是實際上卻是 1 個小時。 這是因為queue.take()與wait不是原子操作,所以才導(dǎo)致這個問題的發(fā)生,下面是改進后的代碼。
public MyTimer(){
thread = new Thread(()->{
while(true){
//取出隊首,如果到時間了就執(zhí)行。
try {
synchronized (this){
MyTask myTask = queue.take();
long curTime = System.currentTimeMillis();
if(curTime < myTask.getTime()){
queue.put(myTask);
//時間未到,不執(zhí)行
//阻塞一段時間
this.wait(myTask.getTime() - curTime);
}else {
//時間已到,執(zhí)行
myTask.run();
}
}
} catch (InterruptedException e) {
e.printStackTrace();
}
}
});
thread.start();
}2.2 完整代碼
//表示定時器中的任務(wù)
class MyTask implements Comparable<MyTask>{
//要執(zhí)行的任務(wù)內(nèi)容
private Runnable runnable;
//延遲時間
private long time;
public MyTask(Runnable runnable, long time) {
this.runnable = runnable;
this.time = time;
}
//為了便于后面的比較,需要提供 get 方法
public long getTime() {
return time;
}
//表示任務(wù)開始執(zhí)行
public void run(){
this.runnable.run();
}
@Override
public int compareTo(MyTask o) {
return (int)(this.getTime() - o.getTime());
}
}
public class MyTimer {
//掃描線程
private Thread thread;
//優(yōu)先級隊列
private PriorityBlockingQueue<MyTask> queue = new PriorityBlockingQueue<>();
public MyTimer(){
thread = new Thread(()->{
while(true){
//取出隊首,如果到時間了就執(zhí)行。
try {
synchronized (this){
MyTask myTask = queue.take();
long curTime = System.currentTimeMillis();
if(curTime < myTask.getTime()){
queue.put(myTask);
//時間未到,不執(zhí)行
//阻塞一段時間
this.wait(myTask.getTime() - curTime);
}else {
//時間已到,執(zhí)行
myTask.run();
}
}
} catch (InterruptedException e) {
e.printStackTrace();
}
}
});
thread.start();
}
/**
* 這個方法是用來注冊(添加)任務(wù)的
* @param runnable 表示待執(zhí)行的任務(wù)
* @param after 表示多少時間過后執(zhí)行任務(wù)
*/
public void schedule(Runnable runnable,long after){
//添加任務(wù),注意這里的時間是 System.currentTimeMillis() + after
MyTask task = new MyTask(runnable,System.currentTimeMillis() + after);
queue.put(task);
synchronized(this){
this.notify();
}
}
}以上就是Java中定時器java.util.Timer的簡單模擬的詳細(xì)內(nèi)容,更多關(guān)于Java定時器的資料請關(guān)注腳本之家其它相關(guān)文章!
相關(guān)文章
SpringMVC的@InitBinder參數(shù)轉(zhuǎn)換代碼實例
這篇文章主要介紹了SpringMVC的@InitBinder參數(shù)轉(zhuǎn)換代碼實例,文中通過示例代碼介紹的非常詳細(xì),對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價值,需要的朋友可以參考下2019-09-09
詳解Spring Boot中如何自定義SpringMVC配置
這篇文章主要給大家介紹了關(guān)于Spring Boot中如何自定義SpringMVC配置的相關(guān)資料,文中通過示例代碼介紹的非常詳細(xì),對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價值,需要的朋友可以參考下2021-09-09
Java創(chuàng)建student類詳細(xì)代碼例子
這篇文章主要給大家介紹了關(guān)于Java創(chuàng)建student類的相關(guān)資料,學(xué)生類(Student)是一種面向?qū)ο蟮木幊谈拍?其主要用于描述學(xué)生的屬性和行為,文中通過代碼介紹的非常詳細(xì),需要的朋友可以參考下2023-11-11
Java實現(xiàn)圖片轉(zhuǎn)base64完整代碼示例
這篇文章主要給大家介紹了關(guān)于Java實現(xiàn)圖片轉(zhuǎn)base64的相關(guān)資料,Base64是網(wǎng)絡(luò)上最常見的用于傳輸8Bit字節(jié)碼的編碼方式之一,Base64就是一種基于64個可打印字符來表示二進制數(shù)據(jù)的方法,需要的朋友可以參考下2023-12-12
SpringBoot使用ShardingSphere-Proxy的實現(xiàn)示例
ShardingSphere-Proxy是一個獨立的數(shù)據(jù)庫代理層,可以與SpringBoot集成,本文介紹了SpringBoot使用ShardingSphere-Proxy的實現(xiàn)示例,具有一定的參考價值,感興趣的可以了解一下2025-02-02
使用idea搭建spring項目,利用xml文件的形式進行配置方式
本文介紹了如何使用SpringIOC和SpringDI的思想開發(fā)一個打印機模擬程序,實現(xiàn)了靈活配置彩色墨盒或灰色墨盒以及打印頁面大小的功能,通過創(chuàng)建接口和實現(xiàn)類,并在配置文件中進行依賴注入,實現(xiàn)了控制反轉(zhuǎn)2024-11-11

