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

Java多線程案例之定時(shí)器詳解

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

一. 定時(shí)器概述

1. 什么是定時(shí)器

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

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

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

類似于這樣的場(chǎng)景就需要用到定時(shí)器.

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

標(biāo)準(zhǔn)庫(kù)中提供了一個(gè) Timer 類, Timer 類的核心方法為schedule.

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

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

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

public abstract class TimerTask implements Runnable // jdk源碼
序號(hào)方法解釋
1public void schedule(TimerTask task, long delay)指定任務(wù), 延遲多久執(zhí)行該任務(wù)
2public void schedule(TimerTask task, Date time)指定任務(wù), 指定任務(wù)的執(zhí)行時(shí)間
3public void schedule(TimerTask task, long delay, long period)連續(xù)執(zhí)行指定任務(wù), 延遲時(shí)間, 連續(xù)執(zhí)行任務(wù)的時(shí)間間隔, 毫秒為單位
4public void schedule(TimerTask task, Date firstTime, long period)連續(xù)執(zhí)行指定任務(wù), 第一次任務(wù)的執(zhí)行時(shí)間, 連續(xù)執(zhí)行任務(wù)的時(shí)間間隔
5public void scheduleAtFixedRate(TimerTask task, Date firstTime, long period)與方法4作用相同
6public void scheduleAtFixedRate(TimerTask task, long delay, long period)與方法3作用相同
7public void cancel()清空任務(wù)隊(duì)列中的全部任務(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é)束, 即進(jìn)程并沒有結(jié)束, 這是因?yàn)樯厦娴拇a定時(shí)器內(nèi)部是開啟了一個(gè)線程去執(zhí)行任務(wù)的, 雖然任務(wù)執(zhí)行完成了, 但是該線程并沒有銷毀; 這和自己定義一個(gè)線程執(zhí)行完成 run 方法后就自動(dòng)銷毀是不一樣的, Timer 本質(zhì)上是相當(dāng)于線程池, 它緩存了一個(gè)工作線程, 一旦任務(wù)執(zhí)行完成, 該工作線程就處于空閑狀態(tài), 等待下一輪任務(wù).

二. 定時(shí)器的簡(jiǎn)單實(shí)現(xiàn)

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

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

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

    //獲取當(dāng)前任務(wù)的執(zhí)行時(shí)間
    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);
    }
}

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

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

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

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

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

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

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

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

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

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

那么最后基于上面的代碼, 我們來測(cè)試一下這個(gè)定時(shí)器:

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多線程案例之定時(shí)器詳解的文章就介紹到這了,更多相關(guān)Java定時(shí)器內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!

相關(guān)文章

  • java讀寫oracle的blob字段示例

    java讀寫oracle的blob字段示例

    這篇文章主要介紹了java讀寫oracle的blob字段示例,需要的朋友可以參考下
    2014-02-02
  • Springboot @Configuration @bean注解作用解析

    Springboot @Configuration @bean注解作用解析

    這篇文章主要介紹了springboot @Configuration @bean注解作用解析,文中通過示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友可以參考下
    2020-02-02
  • redisson.tryLock()參數(shù)的使用及理解

    redisson.tryLock()參數(shù)的使用及理解

    這篇文章主要介紹了redisson.tryLock()參數(shù)的使用,具有很好的參考價(jià)值,希望對(duì)大家有所幫助,如有錯(cuò)誤或未考慮完全的地方,望不吝賜教
    2024-04-04
  • Spring mvc如何實(shí)現(xiàn)數(shù)據(jù)處理

    Spring mvc如何實(shí)現(xiàn)數(shù)據(jù)處理

    這篇文章主要介紹了Spring mvc如何實(shí)現(xiàn)數(shù)據(jù)處理,文中通過示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友可以參考下
    2020-03-03
  • java實(shí)現(xiàn)合并圖片的方法示例

    java實(shí)現(xiàn)合并圖片的方法示例

    這篇文章主要介紹了java實(shí)現(xiàn)合并圖片的方法,結(jié)合具體實(shí)例形式分析了java基于圖片的讀取、設(shè)置、生成等操作實(shí)現(xiàn)圖片合并功能的相關(guān)實(shí)現(xiàn)技巧,需要的朋友可以參考下
    2017-02-02
  • Java編程基于快速排序的三個(gè)算法題實(shí)例代碼

    Java編程基于快速排序的三個(gè)算法題實(shí)例代碼

    這篇文章主要介紹了Java編程基于快速排序的三個(gè)算法題實(shí)例代碼,小編覺得還是挺不錯(cuò)的,具有一定借鑒價(jià)值,需要的朋友可以參考下
    2018-01-01
  • Java結(jié)構(gòu)型設(shè)計(jì)模式之適配器模式詳解

    Java結(jié)構(gòu)型設(shè)計(jì)模式之適配器模式詳解

    適配器模式,即將某個(gè)類的接口轉(zhuǎn)換成客戶端期望的另一個(gè)接口的表示,主要目的是實(shí)現(xiàn)兼容性,讓原本因?yàn)榻涌诓黄ヅ?,沒辦法一起工作的兩個(gè)類,可以協(xié)同工作。本文將通過示例詳細(xì)介紹適配器模式,需要的可以參考一下
    2022-09-09
  • 關(guān)于ArrayList的動(dòng)態(tài)擴(kuò)容機(jī)制解讀

    關(guān)于ArrayList的動(dòng)態(tài)擴(kuò)容機(jī)制解讀

    這篇文章主要介紹了關(guān)于ArrayList的動(dòng)態(tài)擴(kuò)容機(jī)制解讀,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教
    2022-10-10
  • Java 添加、讀取和刪除 Excel 批注的操作代碼

    Java 添加、讀取和刪除 Excel 批注的操作代碼

    這篇文章主要介紹了Java 添加、讀取和刪除 Excel 批注的操作方法,本文通過實(shí)例代碼給大家介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或工作具有一定的參考借鑒價(jià)值,需要的朋友可以參考下
    2020-04-04
  • Spring Boot處理全局統(tǒng)一異常的兩種方法與區(qū)別

    Spring Boot處理全局統(tǒng)一異常的兩種方法與區(qū)別

    這篇文章主要給大家介紹了關(guān)于Spring Boot處理全局統(tǒng)一異常的兩種方法與區(qū)別,文中通過示例代碼介紹的非常詳細(xì),對(duì)大家學(xué)習(xí)或者使用Spring Boot具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面來一起學(xué)習(xí)學(xué)習(xí)吧
    2019-06-06

最新評(píng)論