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

Java多線程案例之定時器詳解

 更新時間:2023年01月28日 16:38:34   作者:bit榮  
定時器是一種實際開發(fā)中非常常用的組件,?類似于一個?“鬧鐘”,?達到一個設(shè)定的時間之后,?就執(zhí)行某個指定好的代碼。本文主要來和大家聊聊定時器的原理與使用,需要的可以參考一下

一. 定時器概述

1. 什么是定時器

定時器是一種實際開發(fā)中非常常用的組件, 類似于一個 “鬧鐘”, 達到一個設(shè)定的時間之后, 就執(zhí)行某個指定好的代碼.

比如網(wǎng)絡(luò)通信中, 如果對方 500ms 內(nèi)沒有返回數(shù)據(jù), 則斷開連接嘗試重連.

比如一個 Map, 希望里面的某個 key 在 3s 之后過期(自動刪除).

類似于這樣的場景就需要用到定時器.

2. 標準庫中的定時器

標準庫中提供了一個 Timer 類, Timer 類的核心方法為schedule.

Timer類構(gòu)造時內(nèi)部會創(chuàng)建線程, 有下面的四個構(gòu)造方法, 可以指定線程名和是否將定時器內(nèi)部的線程指定為后臺線程(即守護線程), 如果不指定, 定時器對象內(nèi)部的線程默認為前臺線程.

序號構(gòu)造方法解釋
1public Timer()無參, 定時器關(guān)聯(lián)的線程為前臺線程, 線程名為默認值
2public Timer(boolean isDaemon)指定定時器中關(guān)聯(lián)的線程類型, true(后臺線程), false(前臺線程)
3public Timer(String name)指定定時器關(guān)聯(lián)的線程名, 線程類型為前臺線程
4public Timer(String name, boolean isDaemon) 指定定時器關(guān)聯(lián)的線程名和線程類型

schedule 方法是給Timer注冊一個任務(wù), 這個任務(wù)在指定時間后進行執(zhí)行, TimerTask類就是專門描述定時器任務(wù)的一個抽象類, 它實現(xiàn)了Runnable接口.

public abstract class TimerTask implements Runnable // jdk源碼
序號方法解釋
1public void schedule(TimerTask task, long delay)指定任務(wù), 延遲多久執(zhí)行該任務(wù)
2public void schedule(TimerTask task, Date time)指定任務(wù), 指定任務(wù)的執(zhí)行時間
3public void schedule(TimerTask task, long delay, long period)連續(xù)執(zhí)行指定任務(wù), 延遲時間, 連續(xù)執(zhí)行任務(wù)的時間間隔, 毫秒為單位
4public void schedule(TimerTask task, Date firstTime, long period)連續(xù)執(zhí)行指定任務(wù), 第一次任務(wù)的執(zhí)行時間, 連續(xù)執(zhí)行任務(wù)的時間間隔
5public void scheduleAtFixedRate(TimerTask task, Date firstTime, long period)與方法4作用相同
6public void scheduleAtFixedRate(TimerTask task, long delay, long period)與方法3作用相同
7public void cancel()清空任務(wù)隊列中的全部任務(wù), 正在執(zhí)行的任務(wù)不受影響

代碼示例:

import java.util.Timer;
import java.util.TimerTask;

public class TestProgram {
    public static void main(String[] args) {
        Timer timer = new Timer();
        timer.schedule(new TimerTask() {
            @Override
            public void run() {
                System.out.println("執(zhí)行延后3s的任務(wù)!");
            }
        }, 3000);

        timer.schedule(new TimerTask() {
            @Override
            public void run() {
                System.out.println("執(zhí)行延后2s后的任務(wù)!");
            }
        }, 2000);
        
        timer.schedule(new TimerTask() {
            @Override
            public void run() {
                System.out.println("執(zhí)行延后1s的任務(wù)!");
            }
        }, 1000);
    }
}

執(zhí)行結(jié)果:

觀察執(zhí)行結(jié)果, 任務(wù)執(zhí)行結(jié)束后程序并沒有結(jié)束, 即進程并沒有結(jié)束, 這是因為上面的代碼定時器內(nèi)部是開啟了一個線程去執(zhí)行任務(wù)的, 雖然任務(wù)執(zhí)行完成了, 但是該線程并沒有銷毀; 這和自己定義一個線程執(zhí)行完成 run 方法后就自動銷毀是不一樣的, Timer 本質(zhì)上是相當于線程池, 它緩存了一個工作線程, 一旦任務(wù)執(zhí)行完成, 該工作線程就處于空閑狀態(tài), 等待下一輪任務(wù).

二. 定時器的簡單實現(xiàn)

首先, 我們需要定義一個類, 用來描述一個定時器當中的任務(wù), 類要成員要有一個Runnable, 再加上一個任務(wù)執(zhí)行的時間戳, 具體還包含如下內(nèi)容:

  • 構(gòu)造方法, 用來指定任務(wù)和任務(wù)的延遲執(zhí)行時間.
  • 兩個get方法, 分別用來給外部對象獲取該對象的任務(wù)和執(zhí)行時間.
  • 實現(xiàn)Comparable接口, 指定比較方式, 用于判斷定時器任務(wù)的執(zhí)行順序, 每次需要執(zhí)行時間最早的任務(wù).
class MyTask implements Comparable<MyTask>{
    //要執(zhí)行的任務(wù)
    private Runnable runnable;
    //任務(wù)的執(zhí)行時間
    private long time;

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

    //獲取當前任務(wù)的執(zhí)行時間
    public long getTime() {
        return this.time;
    }
    //執(zhí)行任務(wù)
    public void run() {
        runnable.run();
    }

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

然后就需要實現(xiàn)定時器類了, 我們需要使用一個數(shù)據(jù)結(jié)構(gòu)來組織定時器中的任務(wù), 需要每次都能將時間最早的任務(wù)找到并執(zhí)行, 這個情況我們可以考慮用優(yōu)先級隊列(即小根堆)來實現(xiàn), 當然我們還需要考慮線程安全的問題, 所以我們選用優(yōu)先級阻塞隊列 PriorityBlockingQueue 是最合適的, 特別要注意在自定義的任務(wù)類當中要實現(xiàn)比較方式, 或者實現(xiàn)一下比較器也行.

private PriorityBlockingQueue<MyTask> queue = new PriorityBlockingQueue<>();

我們自己實現(xiàn)的定時器類中要有一個注冊任務(wù)的方法, 用來將任務(wù)插入到優(yōu)先級阻塞隊列中;

還需要有一個線程用來執(zhí)行任務(wù), 這個線程是從優(yōu)先級阻塞隊列中取出隊首任務(wù)去執(zhí)行, 如果這個任務(wù)還沒有到執(zhí)行時間, 那么線程就需要把這個任務(wù)再放會隊列當中, 然后線程就進入等待狀態(tài), 線程等待可以使用sleep和wait, 但這里有一個情況需要考慮, 當有新任務(wù)插入到隊列中時, 我們需要喚醒線程重新去優(yōu)先級阻塞隊列拿隊首任務(wù), 畢竟新注冊的任務(wù)的執(zhí)行時間可能是要比前一陣拿到的隊首任務(wù)時間是要早的, 所以這里使用wait進行進行阻塞更合適, 那么喚醒操作就需要使用notify來實現(xiàn)了.

實現(xiàn)代碼如下:

//自己實現(xiàn)的定時器類
class MyTimer {
    //掃描線程
    private Thread t = null;
    //阻塞隊列,存放任務(wù)
    private PriorityBlockingQueue<MyTask> queue = new PriorityBlockingQueue<>();

    public MyTimer() {
        //構(gòu)造掃描線程
        t = new Thread(() -> {
           while (true) {
               //取出隊首元素,檢查隊首元素執(zhí)行任務(wù)的時間
               //時間沒到,再把任務(wù)放回去
               //時間到了,就執(zhí)行任務(wù)
               try {
                   synchronized (this) {
                       MyTask task = queue.take();
                       long curTime = System.currentTimeMillis();
                       if (curTime < task.getTime()) {
                           //時間沒到,放回去
                           queue.put(task);
                           //放回任務(wù)后,不應(yīng)該立即就再次取出該任務(wù)
                           //所以wait設(shè)置一個阻塞等待,以便新任務(wù)到時間或者新任務(wù)來時后再取出來
                           this.wait(task.getTime() - curTime);
                       } else {
                           //時間到了,執(zhí)行任務(wù)
                           task.run();
                       }
                   }
               } catch (InterruptedException e) {
                   throw new RuntimeException(e);

               }
           }
        });
        t.start();
    }

    /**
     * 注冊任務(wù)的方法
     * @param runnable 任務(wù)內(nèi)容
     * @param after 表示在多少毫秒之后執(zhí)行. 形如 1000
     */
    public void schedule (Runnable runnable, long after) {
        //獲取當前時間的時間戳再加上任務(wù)時間
        MyTask task = new MyTask(runnable, System.currentTimeMillis() + after);
        queue.put(task);
        //每次當新任務(wù)加載到阻塞隊列時,需要中途喚醒線程,因為新進來的任務(wù)可能是最早需要執(zhí)行的
        synchronized (this) {
            this.notify();
        }
    }
}

要注意上面掃描線程中的synchronized并不能只要針對wait方法加鎖, 如果只針對wait加鎖的話, 考慮一個極端的情況, 假設(shè)的掃描線程剛執(zhí)行完put方法, 這個線程就被cpu調(diào)度走了, 此時另有一個線程在隊列中插入了新任務(wù), 然后notify喚醒了線程, 而剛剛并沒有執(zhí)行wait阻塞, notify就沒有起到什么作用, 當cpu再調(diào)度到這個線程, 這樣的話如果新插入的任務(wù)要比原來隊首的任務(wù)時間更早, 那么這個新任務(wù)就被錯過了執(zhí)行時間, 這些線程安全問題真是防不勝防啊, 所以我們需要保證這些操作的原子性, 也就是上面的代碼, 擴大鎖的范圍, 保證每次notify都是有效的.

那么最后基于上面的代碼, 我們來測試一下這個定時器:

public class TestDemo23 {
    public static void main(String[] args) {
        MyTimer timer = new MyTimer();
        timer.schedule(new Runnable() {
            @Override
            public void run() {
                System.out.println("2s后執(zhí)行的任務(wù)1");
            }
        }, 2000);

        timer.schedule(new Runnable() {
            @Override
            public void run() {
                System.out.println("2s后執(zhí)行的任務(wù)1");
            }
        }, 1000);
    }
}

執(zhí)行結(jié)果:

到此這篇關(guān)于Java多線程案例之定時器詳解的文章就介紹到這了,更多相關(guān)Java定時器內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!

相關(guān)文章

  • java 單例模式(懶漢式與餓漢式)

    java 單例模式(懶漢式與餓漢式)

    這篇文章主要介紹了java 單例模式的相關(guān)資料,這里對懶漢式與餓漢式都做了實例介紹,需要的朋友可以參考下
    2017-07-07
  • spring web.xml指定配置文件過程解析

    spring web.xml指定配置文件過程解析

    這篇文章主要介紹了spring web.xml指定配置文件過程解析,文中通過示例代碼介紹的非常詳細,對大家的學習或者工作具有一定的參考學習價值,需要的朋友可以參考下
    2020-02-02
  • 幾道java循環(huán)練習題(適合新人)

    幾道java循環(huán)練習題(適合新人)

    這篇文章主要給大家介紹了幾道java循環(huán)練習題,非常適合剛?cè)腴T的java新人,文中通過示例代碼介紹的非常詳細,對大家的學習或者工作具有一定的參考學習價值,需要的朋友們下面隨著小編來一起學習學習吧
    2021-04-04
  • 字符串desede 3des加密示例分享

    字符串desede 3des加密示例分享

    這篇文章主要介紹了字符串desede 3des加密示例,大家參考使用吧
    2014-01-01
  • servlet過濾器(Filter)詳解(九)

    servlet過濾器(Filter)詳解(九)

    這篇文章主要為大家詳細介紹了servlet過濾器Filter的相關(guān)資料,具有一定的參考價值,感興趣的小伙伴們可以參考一下
    2017-09-09
  • Java實現(xiàn)對象復制的方法實例

    Java實現(xiàn)對象復制的方法實例

    這篇文章主要介紹了Java實現(xiàn)對象復制的方法實例,深復制:復制出來的對象中的變量(包括基本類型和字符串)和原來的對象的值都相同,引用對象也會指向復制出來的對象,需要的朋友可以參考下
    2023-08-08
  • Spring中使用事務(wù)嵌套時需要警惕的問題分享

    Spring中使用事務(wù)嵌套時需要警惕的問題分享

    最近項目上有一個使用事務(wù)相對復雜的業(yè)務(wù)場景報錯了。在絕大多數(shù)情況下,都是風平浪靜,沒有問題。其實內(nèi)在暗流涌動,在有些異常情況下就會報錯,這種偶然性的問題很有可能就會在暴露到生產(chǎn)上造成事故,那究竟是怎么回事呢?本文就來簡單講講
    2023-04-04
  • Maven添加reactor依賴失敗的解決方案

    Maven添加reactor依賴失敗的解決方案

    起初是自己在學spring boot3,結(jié)果到了reactor這一部分的時候,在項目的pom.xml文件中添加下列依賴報錯,接下來通過本文給大家介紹Maven添加reactor依賴失敗的解決方案,需要的朋友可以參考下
    2024-06-06
  • Spring Boot使用GridFS實現(xiàn)文件的上傳和下載方式

    Spring Boot使用GridFS實現(xiàn)文件的上傳和下載方式

    這篇文章主要介紹了Spring Boot使用GridFS實現(xiàn)文件的上傳和下載方式,具有很好的參考價值,希望對大家有所幫助。如有錯誤或未考慮完全的地方,望不吝賜教
    2021-10-10
  • IDEA maven compile報錯OutOfMemoryError(內(nèi)存溢出)解決及jvm分析

    IDEA maven compile報錯OutOfMemoryError(內(nèi)存溢出)解決及jvm分析

    遇到Maven編譯時報OutOfMemoryError錯誤通常因為默認的堆內(nèi)存大小不足,本文就來介紹一下OutOfMemoryError(內(nèi)存溢出)解決,具有一定的參考價值,感興趣的可以了解一下
    2024-10-10

最新評論

敦化市| 宿州市| 大宁县| 双江| 台南市| 改则县| 长顺县| 东乌珠穆沁旗| 隆回县| 海兴县| 桑植县| 阿拉尔市| 长垣县| 阿克苏市| 浦江县| 武乡县| 竹山县| 白朗县| 斗六市| 岳西县| 施秉县| 七台河市| 重庆市| 芒康县| 五大连池市| 紫金县| 思南县| 汉阴县| 江源县| 突泉县| 义乌市| 额尔古纳市| 山东省| 开江县| 武山县| 调兵山市| 桓台县| 肥西县| 金门县| 清原| 沁水县|