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

Java延遲隊列DelayQueue原理詳解

 更新時間:2023年12月07日 09:38:59   作者:外星喵  
這篇文章主要介紹了Java延遲隊列DelayQueue原理詳解,DelayQueue 是一個通過PriorityBlockingQueue實現(xiàn)延遲獲取元素的無界隊列無界阻塞隊列,其中添加進該隊列的元素必須實現(xiàn)Delayed接口,而且只有在延遲期滿后才能從中提取元素,需要的朋友可以參考下

什么是DelayQueue(延時隊列)

DelayQueue 是一個通過PriorityBlockingQueue實現(xiàn)延遲獲取元素的無界隊列無界阻塞隊列,其中添加進該隊列的元素必須實現(xiàn)Delayed接口(指定延遲時間),而且只有在延遲期滿后才能從中提取元素。

什么是PriorityBlockingQueue(優(yōu)先隊列)

PriorityBlockingQueue是一個支持優(yōu)先級的無界阻塞隊列,隊列的元素默認情況下元素采用自然順序升序排列,或者根據(jù)構造隊列時提供的 Comparator 進行排序,具體取決于所使用的構造方法。

需要注意的是不能保證同優(yōu)先級元素的順序。

PriorityBlockingQueue也是基于最小二叉堆實現(xiàn),使用基于CAS實現(xiàn)的自旋鎖來控制隊列的動態(tài)擴容,保證了擴容操作不會阻塞take操作的執(zhí)行。

DelayQueue使用場景

DelayQueue可以運用在以下應用場景:

  1. 緩存系統(tǒng)的設計:可以用DelayQueue保存緩存元素的有效期,使用一個線程循環(huán)查詢DelayQueue,一旦能從DelayQueue中獲取元素時,表示緩存有效期到了。
  2. 定時任務調(diào)度。使用DelayQueue保存當天將會執(zhí)行的任務和執(zhí)行時間,一旦從DelayQueue中獲取到任務就開始執(zhí)行,從比如TimerQueue就是使用DelayQueue實現(xiàn)的。

DelayQueue原理

DelayQueue的泛型參數(shù)需要實現(xiàn)Delayed接口,Delayed接口繼承了Comparable接口,DelayQueue內(nèi)部使用非線程安全的優(yōu)先隊列(PriorityQueue),并使用Leader/Followers模式,最小化不必要的等待時間。DelayQueue不允許包含null元素。

Leader/Followers模式:

有若干個線程(一般組成線程池)用來處理大量的事件

有一個線程作為領導者,等待事件的發(fā)生;其他的線程作為追隨者,僅僅是睡眠。

假如有事件需要處理,領導者會從追隨者中指定一個新的領導者,自己去處理事件。

喚醒的追隨者作為新的領導者等待事件的發(fā)生。

處理事件的線程處理完畢以后,就會成為追隨者的一員,直到被喚醒成為領導者。

假如需要處理的事件太多,而線程數(shù)量不夠(能夠動態(tài)創(chuàng)建線程處理另當別論),則有的事件可能會得不到處理。

所有線程會有三種身份中的一種:leader和follower,以及一個干活中的狀態(tài):proccesser。它的基本原則就是,永遠最多只有一個leader。而所有follower都在等待成為leader。線程池啟動時會自動產(chǎn)生一個Leader負責等待網(wǎng)絡IO事件,當有一個事件產(chǎn)生時,Leader線程首先通知一個Follower線程將其提拔為新的Leader,然后自己就去干活了,去處理這個網(wǎng)絡事件,處理完畢后加入Follower線程等待隊列,等待下次成為Leader。這種方法可以增強CPU高速緩存相似性,及消除動態(tài)內(nèi)存分配和線程間的數(shù)據(jù)交換。

DelayQueue源碼解析

DelayQueue屬性

//可重入同步鎖
private final transient ReentrantLock lock = new ReentrantLock();
//DelayQueue的實現(xiàn)依賴于PriorityQueue(優(yōu)先隊列)
private final PriorityQueue<E> q = new PriorityQueue<E>();
//第一個等待某個延時對象的線程,在延時對象還沒有到期時其他線程看到這個leader不為null,那么就直接wait
//主要是為了避免大量線程在同一時間點喚醒,導致大量的競爭,反而影響性能
private Thread leader = null;
//條件隊列,用于wait線程
private final Condition available = lock.newCondition();

DelayQueue構造方法

    //從上面屬性就可以看出,DelayQueue采用了餓漢模式,調(diào)用構造方法即創(chuàng)建了隊列實例
    public DelayQueue() {}
    /**
     * 創(chuàng)建一個DelayQueue,最初包含給定的Collection實例集合。
     * @param c 最初包含的元素集合
     */
    public DelayQueue(Collection<? extends E> c) {
        this.addAll(c);
    }

DelayQueue主要方法

offer添加元素

public boolean offer(E e) {
    final ReentrantLock lock = this.lock;
    lock.lock();
    try {
        //調(diào)用優(yōu)先隊列
        q.offer(e);
        //檢驗元素是否為隊首,是則設置 leader 為null, 并喚醒一個消費線程  
        if (q.peek() == e) {
            leader = null;
            available.signal();
        }
        return true;
    } finally {
        lock.unlock();
    }
}

take獲取元素

public E take() throws InterruptedException {
    final ReentrantLock lock = this.lock;
    lock.lockInterruptibly();
    try {
        for (;;) {
            //從優(yōu)先隊列中獲取第一個元素,peek方法不會刪除元素
            E first = q.peek();
            //如果獲取不到數(shù)據(jù),則調(diào)用available.await()進入阻塞狀態(tài)
            if (first == null)
                available.await();
            else {
                //獲取當前延時對象是否到期
                long delay = first.getDelay(NANOSECONDS);
                //到期那么返回這個延時對象
                if (delay <= 0)
                    return q.poll();
                first = null; // 
                //leader不為空,表明已經(jīng)有其他線程在等待這個延時對象了
                //為什么不available.awaitNanos(delay)呢?這將會導致大量的線程在同一時間點被喚醒,然后去競爭
                //這個到期的延時任務,影響性能,還不如直接將他們無時間限制的wait,leader線程或者其他新進來的線程獲取到延時對象后,去喚醒
                //讓他們?nèi)ジ偁幭乱粋€延時對象
                if (leader != null)
                    available.await();
                else {
                    Thread thisThread = Thread.currentThread();
                    leader = thisThread;
                    try {
                        //指定納秒級別線程阻塞時間,當前wait住的線程被喚醒后有可能與其他線程競爭失敗,就會進入了同步隊列阻塞,那個搶到鎖的線程就會取走這個延時對象
                        available.awaitNanos(delay);
                    } finally {
                        //leader線程被喚醒并獲取到鎖之后會將leader設置為空
                        if (leader == thisThread)
                            leader = null;
                    }
                }
            }
        }
    } finally {
        //leader為空并且隊列不為空,那么喚醒正在等待的線程
        if (leader == null && q.peek() != null)
            available.signal();
        lock.unlock();  //釋放鎖
    }
}

從優(yōu)先隊列中取值,如果取到的延時節(jié)點已經(jīng)已經(jīng)到期,那么直接返回,如果還沒有到期并且已經(jīng)有其他線程在執(zhí)行delay時間等待了(也就是leader線程),那么掛起自己(避免延時 相同時間造成大量線程同時喚醒), leader線程在指定delay時間后主動喚醒,然后取競爭鎖,如果競爭成功,那么很大概率可以獲取到延時節(jié)點,如果競爭失敗,將被阻塞。

remove刪除元素

public boolean remove(Object o) {
    final ReentrantLock lock = this.lock;
    lock.lock();
    try {
        return q.remove(o);
    } finally {
        lock.unlock();
    }
}

Delayed接口

使用DelayQueue的話,放入該隊列的對象必須實現(xiàn)Delayed接口,實現(xiàn)的接口中有兩個參數(shù):延遲時間單位,優(yōu)先級規(guī)則,take方法會根據(jù)規(guī)則按照優(yōu)先級執(zhí)行

Delayed接口源碼:

public interface Delayed extends Comparable<Delayed> {
    /**
     * 返回與此對象關聯(lián)的剩余延遲(給定的時間單位)。
     * @param unit 時間單位
     * @返回剩余延遲;零值或負值表示 延遲已過期
     */
    long getDelay(TimeUnit unit);
}

因為Delayed繼承了Comparable,所以還需要實現(xiàn)compareTo方法,具體實現(xiàn)如下:

class MyDelay implements Delayed {
    long delayTime; // 延遲時間
    long expire; // 過期時間
    public MyDelay(long delayTime, Thread t) {
        this.delayTime = delayTime;
        // 過期時間 = 當前時間 + 延遲時間
        this.expire = System.currentTimeMillis() + delayTime;
    }
    /**
     * 剩余時間 = 到期時間 - 當前時間
     */
    @Override
    public long getDelay(TimeUnit unit) {
        return unit.convert(this.expire - System.currentTimeMillis(), TimeUnit.MILLISECONDS);
    }
    /**
     * 優(yōu)先級規(guī)則:兩個任務比較,時間短的優(yōu)先執(zhí)行
     */
    @Override
    public int compareTo(Delayed o) {
        long f = this.getDelay(TimeUnit.MILLISECONDS) - o.getDelay(TimeUnit.MILLISECONDS);
        return (int) f;
    }
}

使用示例

實現(xiàn)Delayed接口:

class MyDelay<T> implements Delayed {
    long delayTime; // 延遲時間
    long expire; // 過期時間
    T data;
    public MyDelay(long delayTime, T t) {
        this.delayTime = delayTime;
        // 過期時間 = 當前時間 + 延遲時間
        this.expire = System.currentTimeMillis() + delayTime;
        data = t;
    }
    /**
     * 剩余時間 = 到期時間 - 當前時間
     */
    @Override
    public long getDelay(TimeUnit unit) {
        return unit.convert(this.expire - System.currentTimeMillis(), TimeUnit.MILLISECONDS);
    }
    /**
     * 優(yōu)先級規(guī)則:兩個任務比較,時間短的優(yōu)先執(zhí)行
     */
    @Override
    public int compareTo(Delayed o) {
        long f = this.getDelay(TimeUnit.MILLISECONDS) - o.getDelay(TimeUnit.MILLISECONDS);
        return (int) f;
    }
    @Override
    public String toString() {
        return "delayTime=" + delayTime +
                ", expire=" + expire +
                ", data=" + data;
    }
}

測試用例如下:

public class DelayQueueDemo {
    static BlockingQueue<Delayed> queue = new DelayQueue();
    public static void main(String[] args) throws InterruptedException {
        queue.add(new MyDelay(8, "第一次添加任務"));
        queue.add(new MyDelay(3, "第二次添加任務"));
        queue.add(new MyDelay(5, "第三次添加任務"));
        while (!queue.isEmpty()) {
            Delayed delayed = queue.take();
            System.out.println(delayed);
        }
    }
}

輸出如下:

delayTime=3, expire=1625902338874, data=第二次添加任務
delayTime=5, expire=1625902338876, data=第三次添加任務
delayTime=8, expire=1625902338879, data=第一次添加任務

總結

DelayQueue其實采用了裝飾器模式,在對PriorityQueue進行包裝下增加了延時時間獲取元素的功能,其主要特點歸納如下:

  1. DelayQueue是一個無界阻塞隊列,隊列內(nèi)部使用PriorityQueue來實現(xiàn)。
  2. 進入隊列的元素必須實現(xiàn)Delayed接口,在創(chuàng)建元素時可以指定多久才能從隊列中獲取當前元素,只有在延遲期滿時才能從中提取元素;
  3. 該隊列頭部是延遲期滿后保存時間最長的Delayed元素;
  4. 如果沒有延遲未過期元素,且隊列沒有頭部,并且poll將返回null;
  5. 當一個元素的getDelay(TimeUnit.NANOSECONDS)方法返回一個小于等于0的值時,表示該元素已過期;
  6. 無法使用poll或take移除未到期的元素,也不會將這些元素作為正常元素對待;例如:size方法返回到期和未到期元素的計數(shù)之和。
  7. 此隊列不允許使用null元素。

到此這篇關于Java延遲隊列DelayQueue原理詳解的文章就介紹到這了,更多相關延遲隊列DelayQueue原理內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關文章希望大家以后多多支持腳本之家!

相關文章

  • Java中ByteBuddy動態(tài)字節(jié)碼操作庫的使用技術指南

    Java中ByteBuddy動態(tài)字節(jié)碼操作庫的使用技術指南

    ByteBuddy?是一個功能強大的?Java?字節(jié)碼操作庫,可以幫助開發(fā)者在運行時動態(tài)生成和修改類,而無需直接接觸復雜的?ASM?API,本文給大家介紹了Java?ByteBuddy動態(tài)字節(jié)碼操作庫的使用技術指南,需要的朋友可以參考下
    2025-04-04
  • Java使用遞歸法解決漢諾塔問題的代碼示例

    Java使用遞歸法解決漢諾塔問題的代碼示例

    這篇文章主要介紹了Java使用遞歸法解決漢諾塔問題的代碼示例,漢諾塔問題是使用遞歸解決問題的經(jīng)典范例,用到的算法非常簡單,需要的朋友可以參考下
    2016-04-04
  • 詳解java中List中set方法和add方法的區(qū)別

    詳解java中List中set方法和add方法的區(qū)別

    本文主要介紹了詳解java中List中set方法和add方法的區(qū)別,文中通過示例代碼介紹的非常詳細,對大家的學習或者工作具有一定的參考學習價值,需要的朋友們下面隨著小編來一起學習學習吧
    2022-08-08
  • JDK自帶的序列化方式優(yōu)缺點及實現(xiàn)原理面試精講

    JDK自帶的序列化方式優(yōu)缺點及實現(xiàn)原理面試精講

    這篇文章主要為大家介紹了JDK自帶的序列化方式優(yōu)缺點及實現(xiàn)原理面試精講,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進步,早日升職加薪
    2023-10-10
  • java線程死鎖代碼示例

    java線程死鎖代碼示例

    這篇文章主要介紹了java線程死鎖代碼示例,分享了一個簡單線程死鎖的例子,需要的朋友可以參考下。
    2017-11-11
  • Hibernate組件映射代碼詳解

    Hibernate組件映射代碼詳解

    這篇文章主要介紹了Hibernate組件映射代碼詳解,分享了相關代碼示例,小編覺得還是挺不錯的,具有一定借鑒價值,需要的朋友可以參考下
    2018-02-02
  • 解讀.idea文件的使用及說明

    解讀.idea文件的使用及說明

    文章介紹了IntelliJ IDEA項目中的.idea文件夾及其作用,包括編譯配置、工作空間配置、項目標識文件、編碼配置、jar包信息以及插件配置等,同時,文章提醒在版本控制時應排除.idea文件夾,以避免版本沖突
    2025-01-01
  • Springboot升級到2.7.2結合nacos遇到的坑及解決

    Springboot升級到2.7.2結合nacos遇到的坑及解決

    這篇文章主要介紹了Springboot升級到2.7.2結合nacos遇到的坑及解決,具有很好的參考價值,希望對大家有所幫助,如有錯誤或未考慮完全的地方,望不吝賜教
    2024-06-06
  • java static塊和構造函數(shù)的實例詳解

    java static塊和構造函數(shù)的實例詳解

    這篇文章主要介紹了java static塊和構造函數(shù)的實例詳解的相關資料,希望通過本文能幫助到大家,讓大家理解掌握Java static關鍵字的函數(shù)方法,需要的朋友可以參考下
    2017-09-09
  • SpringBoot前后端json數(shù)據(jù)交互的全過程記錄

    SpringBoot前后端json數(shù)據(jù)交互的全過程記錄

    現(xiàn)在大多數(shù)互聯(lián)網(wǎng)項目都是采用前后端分離的方式開發(fā),下面這篇文章主要給大家介紹了關于SpringBoot前后端json數(shù)據(jù)交互的相關資料,文中通過實例代碼介紹的非常詳細,需要的朋友可以參考下
    2022-03-03

最新評論