最新国产好看的视频,伊人天堂AV在线,国产Aaaaaa视频,蜜臀视频在线观看一区,人妻av色图,密臀久久久精品影片,青青视频免费观看毛片,久草在线观看视,国产三级精品色情在线

一文帶你搞懂Java定時器Timer的使用

 更新時間:2023年01月09日 15:30:57   作者:熬夜磕代碼丶  
定時器類似于我們生活中的鬧鐘,可以設(shè)定一個時間來提醒我們。而定時器是指定一個時間去執(zhí)行一個任務(wù),讓程序去代替人工準(zhǔn)時操作。本文就來聊聊Java定時器Timer的使用,需要的可以參考一下

一、定時器是什么

定時器類似于我們生活中的鬧鐘,可以設(shè)定一個時間來提醒我們。

而定時器是指定一個時間去執(zhí)行一個任務(wù),讓程序去代替人工準(zhǔn)時操作。

標(biāo)準(zhǔn)庫中的定時器: Timer

方法作用
void schedule(TimerTask task, long delay)指定delay時間之后(單位毫秒)執(zhí)行任務(wù)task
public static void main(String[] args) {
        Timer timer = new Timer();
        timer.schedule(new TimerTask() {
            @Override
            public void run() {
                System.out.println("定時器任務(wù)! ");
            }
        },1000);
    }

這段程序就是創(chuàng)建一個定時器,然后提交一個1000s后執(zhí)行的任務(wù)。

二、自定義定時器

我們自己實(shí)現(xiàn)一個定時器的前提是我們需要弄清楚定時器都有什么:

1.一個掃描線程,負(fù)責(zé)來判斷任務(wù)是否到時間需要執(zhí)行

2.需要有一個數(shù)據(jù)結(jié)構(gòu)來保存我們定時器中提交的任務(wù)

創(chuàng)建一個掃描線程相對比較簡單,我們需要確定一個數(shù)據(jù)結(jié)構(gòu)來保存我們提交的任務(wù),我們提交過來的任務(wù),是由任務(wù)和時間組成的,我們需要構(gòu)建一個Task對象,數(shù)據(jù)結(jié)構(gòu)我們這里使用優(yōu)先級隊(duì)列,因?yàn)槲覀兊娜蝿?wù)是有時間順序的,具有一個優(yōu)先級,并且要保證在多線程下是安全的,所以我們這里使用:PriorityBlockingQueue比較合適。

首先我們構(gòu)造一個Task對象

class MyTask {
    //即將執(zhí)行的任務(wù)
    private Runnable runnable;
    //在多久后執(zhí)行
    private long time;

    public MyTask(Runnable runnable, long time) {
        this.runnable = runnable;
        this.time = time;
    }

    public long getTime() {
        return time;
    }
    
    //執(zhí)行任務(wù)
    public void run() {
        runnable.run();
    }
}

MyTimer類:

public class MyTimer {
    //掃描線程
    private Thread t;
    //創(chuàng)建一個阻塞優(yōu)先級隊(duì)列,用來保存提交的Task對象
    private PriorityBlockingQueue<MyTask> queue = new PriorityBlockingQueue<>();
    private Object locker = new Object();

    //提交任務(wù)的方法
    public void schedule(Runnable runnable,long time) {
        //這里我們的時間換算一下,保存實(shí)際執(zhí)行的時間
        MyTask task = new MyTask(runnable,System.currentTimeMillis() +  time);
        queue.put(task);
    }

    //構(gòu)建掃描線程
    public MyTimer() {
        t = new Thread(() -> {
           //我們?nèi)〕鲫?duì)列中時間最近的元素
            while (true) {
                try {
                    MyTask task = queue.take();
                    long curTime = System.currentTimeMillis();
                    if(curTime < task.getTime()) {
                        //證明還沒到執(zhí)行的時間,再放進(jìn)隊(duì)列
                        queue.put(task);
                    } else {
                        //到時間了,執(zhí)行任務(wù)
                        task.run();
                    }
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        });
    }
    t.start();
}

雖然我們大體已經(jīng)寫出來了,但是我們這個定時器實(shí)現(xiàn)的還有一些問題。

問題1:既然我們是優(yōu)先級隊(duì)列,我們再阻塞優(yōu)先級隊(duì)列中放入Task對象時,是根據(jù)什么建立堆的?

我們發(fā)現(xiàn)當(dāng)我們運(yùn)行程序時,我們的程序也會報(bào)這樣的錯誤。

class MyTask implements Comparable<MyTask>{
    //即將執(zhí)行的任務(wù)
    private Runnable runnable;
    //在多久后執(zhí)行
    private long time;

    public MyTask(Runnable runnable, long time) {
        this.runnable = runnable;
        this.time = time;
    }

    public long getTime() {
        return time;
    }

    //執(zhí)行任務(wù)
    public void run() {
        runnable.run();
    }

    @Override
    public int compareTo(MyTask o) {
        return (int) (this.time - o.time);
    }
}

我們需要實(shí)現(xiàn)Comparable接口并且重寫compareTo方法,指明我們是根據(jù)時間來決定在隊(duì)列中的優(yōu)先級。

2.我們的掃描線程,掃描的速度太快,造成了不必要的CPU資源浪費(fèi)。

比如我們早上8.00提交了一個中午12.00的任務(wù),那么我們這樣的程序就會從8.00一直循環(huán)幾十億次,而這樣的等待是沒有任何意義的。

更合理的方式是,不要在這里忙等,而是“阻塞式”等待。

public MyTimer() {
        t = new Thread(() -> {
           //我們?nèi)〕鲫?duì)列中時間最近的元素
            while (true) {
                try {
                    MyTask task = queue.take();
                    long curTime = System.currentTimeMillis();
                    if(curTime < task.getTime()) {
                        //證明還沒到執(zhí)行的時間,再放進(jìn)隊(duì)列
                        queue.put(task);
                        synchronized (locker) {
                            locker.wait(task.getTime() - curTime);
                        }
                    } else {
                        //到時間了,執(zhí)行任務(wù)
                        task.run();
                    }
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        });
    }
    t.start();

我們重寫一下掃描線程,進(jìn)行修改,當(dāng)我們判斷隊(duì)列中最近的一個任務(wù)的時間都沒到時,我們的掃描線程就進(jìn)行阻塞等待,這里我們使用的不是wait(),而是wait(long time),我們傳入的參數(shù)是要執(zhí)行的時間和當(dāng)前時間的差值,有的同學(xué)可能會問了,那這樣執(zhí)行的時候和預(yù)期執(zhí)行的時間不就有出入了嘛?

因?yàn)槲覀兂绦蚶锏亩〞r操作,本來就難以做到非常準(zhǔn)確,因?yàn)椴僮飨到y(tǒng)調(diào)度是隨機(jī)的,有一定的時間開銷,存在ms的誤差都是相當(dāng)正常的,不影響我們的正常使用。

我們上面進(jìn)行阻塞等待,難道就傻傻的等到時間到了自動喚醒嘛? 有沒有啥特殊情況呢?這里是有的,比如我們設(shè)定了一個阻塞到12點(diǎn)在喚醒,但我們又提交了一個10點(diǎn)的新任務(wù),那么我們就應(yīng)該提前喚醒了,所以我們應(yīng)該在每次提交任務(wù)后都進(jìn)行主動喚醒,再由我們掃描線程決定是執(zhí)行還是繼續(xù)阻塞等待。

public void schedule(Runnable runnable,long time) {
        //這里我們的時間換算一下,保存實(shí)際執(zhí)行的時間
        MyTask task = new MyTask(runnable,System.currentTimeMillis() +  time);
        queue.put(task);
        synchronized (locker) {
            locker.notify();
        }
    }

即使我們現(xiàn)在所有正常的情況都考慮到了,但是我們這里仍然存在一種極端的情況。

假設(shè)我們的掃描線程剛執(zhí)行完put方法,這個線程就被cpu調(diào)度走了,此時我們的另一個線程調(diào)用了schedule,添加了新任務(wù),新任務(wù)是10點(diǎn)執(zhí)行,然后notify,因?yàn)槲覀儾]有wait(),所以相當(dāng)于這里是空的notify,然后我們的線程調(diào)度回來去執(zhí)行wait()方法,但是我們的時間差仍然是之前算好的時間差,從8.00點(diǎn)到12.00點(diǎn),這樣就會產(chǎn)生很大的錯誤。

這里造成這樣的問題,是因?yàn)槲覀兊膖ake操作和wait操作不是原子的,我們需要在take和wait之間加上鎖,保證每次notify的時候,都在wait中。

public MyTimer() {
        t = new Thread(() -> {
            while (true) {
                try {
                    synchronized (locker) {
                        MyTask Task = queue.take();
                        long curTime = System.currentTimeMillis();
                        if (curTime < Task.getTime()) {
                            queue.put(Task);
                            locker.wait(Task.getTime() - curTime);
                        } else {
                            Task.run();
                        }
                    }
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        });
        t.start();

以上就是一文帶你搞懂Java定時器Timer的使用的詳細(xì)內(nèi)容,更多關(guān)于Java定時器Timer的資料請關(guān)注腳本之家其它相關(guān)文章!

相關(guān)文章

最新評論

抚松县| 潍坊市| 游戏| 柳江县| 灵寿县| 保德县| 庆云县| 祁门县| 阜新市| 营口市| 民丰县| 精河县| 拉孜县| 定西市| 仲巴县| 滨海县| 称多县| 陆河县| 光山县| 霍城县| 晋江市| 临汾市| 桃源县| 香港 | 安丘市| 咸丰县| 滁州市| 察雅县| 洪泽县| 蒲江县| 邳州市| 淅川县| 绥江县| 休宁县| 鄄城县| 云林县| 上杭县| 清远市| 江孜县| 灵石县| 抚顺市|