欧美bbbwbbbw肥妇,免费乱码人妻系列日韩,一级黄片

一文帶你搞懂Java定時(shí)器Timer的使用

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

一、定時(shí)器是什么

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

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

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

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

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

二、自定義定時(shí)器

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

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

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

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

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

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)建一個(gè)阻塞優(yōu)先級(jí)隊(duì)列,用來保存提交的Task對(duì)象
    private PriorityBlockingQueue<MyTask> queue = new PriorityBlockingQueue<>();
    private Object locker = new Object();

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

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

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

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

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

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ù)時(shí)間來決定在隊(duì)列中的優(yōu)先級(jí)。

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

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

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

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

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

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

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

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

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

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

這里造成這樣的問題,是因?yàn)槲覀兊膖ake操作和wait操作不是原子的,我們需要在take和wait之間加上鎖,保證每次notify的時(shí)候,都在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定時(shí)器Timer的使用的詳細(xì)內(nèi)容,更多關(guān)于Java定時(shí)器Timer的資料請(qǐng)關(guān)注腳本之家其它相關(guān)文章!

相關(guān)文章

最新評(píng)論