PriorityBlockingQueue詳解

来源:https://www.cnblogs.com/chafry/archive/2022/10/12/16783035.html
-Advertisement-
Play Games

PriorityBlockingQueue介紹 【1】PriorityBlockingQueue是一個無界的基於數組的優先順序阻塞隊列,數組的預設長度是11,也可以指定數組的長度,且可以無限的擴充,直到資源消耗盡為止,每次出隊都返回優先順序別最高的或者最低的元素。預設情況下元素採用自然順序升序排序,當然 ...


PriorityBlockingQueue介紹

  【1】PriorityBlockingQueue是一個無界的基於數組的優先順序阻塞隊列,數組的預設長度是11,也可以指定數組的長度,且可以無限的擴充,直到資源消耗盡為止,每次出隊都返回優先順序別最高的或者最低的元素。預設情況下元素採用自然順序升序排序,當然我們也可以通過構造函數來指定Comparator來對元素進行排序。需要註意的是PriorityBlockingQueue不能保證同優先順序元素的順序。

  【2】優先順序隊列PriorityQueue: 隊列中每個元素都有一個優先順序,出隊的時候,優先順序最高的先出

PriorityBlockingQueue的源碼分析

  【1】屬性值

//預設容量
private static final int DEFAULT_INITIAL_CAPACITY = 11;

//最大容量設定
private static final int MAX_ARRAY_SIZE = Integer.MAX_VALUE - 8;

//存放數據的數組
private transient Object[] queue;

//元素個數
private transient int size;

//排序規則(比較器)
private transient Comparator<? super E> comparator;

//獨占鎖
private final ReentrantLock lock;

//隊列為空的時候的阻塞隊列
private final Condition notEmpty;

//用於分配的CAS自旋鎖
private transient volatile int allocationSpinLock;

//只用於序列化的普通優先隊列
private PriorityQueue<E> q;

 

  【2】構造函數

//沒有指定容量,則容量預設11
public PriorityBlockingQueue() {
    this(DEFAULT_INITIAL_CAPACITY, null);
}

//有指定容量則容量為指定數值
public PriorityBlockingQueue(int initialCapacity) {
    this(initialCapacity, null);
}

//初始化所需要的屬性值
public PriorityBlockingQueue(int initialCapacity, Comparator<? super E> comparator) {
    if (initialCapacity < 1)
        throw new IllegalArgumentException();
    this.lock = new ReentrantLock();
    this.notEmpty = lock.newCondition();
    this.comparator = comparator;
    this.queue = new Object[initialCapacity];
}

 

  【3】核心方法分析

    1)核心擴容函數

//擴容函數
private void tryGrow(Object[] array, int oldCap) {
    lock.unlock(); //必須釋放然後重新獲得主鎖,這一步的意義在於所有操作共用一把鎖,在進行擴容時(因為寫已滿),釋放鎖(不能寫,要等待擴容完才能寫),提供給讀操作
    Object[] newArray = null;
    // CAS 輕量級鎖加鎖,避免併發擴容
    if (allocationSpinLock == 0 && UNSAFE.compareAndSwapInt(this, allocationSpinLockOffset, 0, 1)) {
        try {
            // 擴容步長,舊值小於64時,變為兩倍+2。大於64時,變為1.5倍。
            int newCap = oldCap + ((oldCap < 64) ? (oldCap + 2) :  (oldCap >> 1));
            // 超過最大容量,記憶體溢出
            if (newCap - MAX_ARRAY_SIZE > 0) {    // possible overflow
                int minCap = oldCap + 1;
                if (minCap < 0 || minCap > MAX_ARRAY_SIZE)
                    throw new OutOfMemoryError();
                newCap = MAX_ARRAY_SIZE;
            }
            // 創建新數組
            if (newCap > oldCap && queue == array)
                newArray = new Object[newCap];
        } finally {
            allocationSpinLock = 0;
        }
    }
    // 併發擴容時,線程讓出 cpu 執行時間,給其他線程執行,自己稍後執行,原因:加鎖不成功必然有其他線程也在擴容,在等待過程中讓出資源給其他線程利用
    if (newArray == null) 
        Thread.yield();
    //重新加鎖
    lock.lock();
    //變更記憶體指向,利用記憶體拷貝複製舊數據
    if (newArray != null && queue == array) {
        queue = newArray;
        System.arraycopy(array, 0, newArray, 0, oldCap);
    }
}

 

    2)入隊方法

public void put(E e) {
    offer(e); // never need to block
}
public boolean add(E e) {
    return offer(e);
}

public boolean offer(E e) {
    if (e == null)
        throw new NullPointerException();
    final ReentrantLock lock = this.lock;
    lock.lock();
    int n, cap;
    Object[] array;
    while ((n = size) >= (cap = (array = queue).length))
        //自旋擴容
        tryGrow(array, cap);
    try {
        Comparator<? super E> cmp = comparator;
        //根據比較器採用填充的方式
        if (cmp == null)
            siftUpComparable(n, e, array);
        else
            siftUpUsingComparator(n, e, array, cmp);
        size = n + 1;
        notEmpty.signal();
    } finally {
        lock.unlock();
    }
    return true;
}

 

    4)隊列填入方式

      代碼展示

private static <T> void siftUpComparable(int k, T x, Object[] array) {
    Comparable<? super T> key = (Comparable<? super T>) x;
    while (k > 0) {
        int parent = (k - 1) >>> 1;
        Object e = array[parent];
        if (key.compareTo((T) e) >= 0)
            break;
        array[k] = e;
        k = parent;
    }
    array[k] = key;
}
//雷同上面的不過比較器採用傳入的
private static <T> void siftUpUsingComparator(int k, T x, Object[] array,  Comparator<? super T> cmp) {
    while (k > 0) {
        int parent = (k - 1) >>> 1;
        Object e = array[parent];
        if (cmp.compare(x, (T) e) >= 0)
            break;
        array[k] = e;
        k = parent;
    }
    array[k] = x;
}

 

      圖解說明

        1.利用了滿二叉樹的理念,(k - 1) >>> 1可以獲得存入節點的父節點下標,然後進行比較。若判斷應該上升,則將父節點置於k存儲的地方。

        2.重覆迴圈,知道二叉樹root節點,或者已經找到了合適的位置

           

 

    4)出隊方法

public E poll() {
    final ReentrantLock lock = this.lock;
    lock.lock();
    try {
        return dequeue();
    } finally {
        lock.unlock();
    }
}

public E take() throws InterruptedException {
    final ReentrantLock lock = this.lock;
    lock.lockInterruptibly();
    E result;
    try {
        while ( (result = dequeue()) == null)
            notEmpty.await();
    } finally {
        lock.unlock();
    }
    return result;
}

private E dequeue() {
    int n = size - 1;
    if (n < 0)
        return null;
    else {
        Object[] array = queue;
        //將頭節點取出返回
        E result = (E) array[0];
        //將末尾節點當做向頭節點位置存入的節點
        E x = (E) array[n];
        array[n] = null;
        Comparator<? super E> cmp = comparator;
        if (cmp == null)
            siftDownComparable(0, x, array, n);
        else
            siftDownUsingComparator(0, x, array, n, cmp);
        size = n;
        return result;
    }
}

 

    5)隊列修正方式

      代碼展示

private static <T> void siftDownComparable(int k, T x, Object[] array, int n) {
    if (n > 0) {
        Comparable<? super T> key = (Comparable<? super T>)x;
        //過濾掉最後一層的元素(以為滿二叉樹中,最後一層占據的元素就是n/2)
        int half = n >>> 1;           // loop while a non-leaf
        while (k < half) {
            //獲取頭節點的左節點
            int child = (k << 1) + 1; // assume left child is least
            Object c = array[child];
            int right = child + 1;
            //進行左右節點的比較,取小的一邊作為比較,以為優先隊列要確保頭節點是最小的(換而言之,保證子樹下麵的頭節點為子樹裡面最小的即可)
            if (right < n && ((Comparable<? super T>) c).compareTo((T) array[right]) > 0)
                c = array[child = right];
            if (key.compareTo((T) c) <= 0)
                break;
            array[k] = c;
            k = child;
        }
        array[k] = key;
    }
}

private static <T> void siftDownUsingComparator(int k, T x, Object[] array, int n,Comparator<? super T> cmp) {
    if (n > 0) {
        int half = n >>> 1;
        while (k < half) {
            int child = (k << 1) + 1;
            Object c = array[child];
            int right = child + 1;
            if (right < n && cmp.compare((T) c, (T) array[right]) > 0)
                c = array[child = right];
            if (cmp.compare(x, (T) c) <= 0)
                break;
            array[k] = c;
            k = child;
        }
        array[k] = x;
    }
}

 

      圖解說明

        1.採用尾結點代替頭節點,然後利用下沉的方式來修正優先隊列裡面的數據。

        2.下沉限制在倒數第二層,以為倒數第二層就會與倒數第一層進行比較了,所以應該進行限制(下標位置超出倒數第二層的最大下標就應該停止了)

        3.其次看的時候,可以把左右子樹都當做一個小的滿二叉樹,不斷逐層劃分,這樣條理更清晰。

           

 

PriorityBlockingQueue總結

  【1】一個支持優先順序排序的無界阻塞隊列,優先順序高的先出隊,優先順序低的後出隊

  【2】數據結構:數組+二叉堆(預設容量11,可指定初始容量,會自動擴容,最大容量是(Integer.MAX_VALUE - 8))

  【3】鎖:ReentrantLock,存取是同一把鎖

  【4】阻塞對象:NotEmpty,出隊,隊列為空時阻塞

  【5】入隊,不阻塞,永遠返回成功,無界;根據比較器進行堆化(排序)自下而上,如果比較器為 null,則按照自然順序排序,傳入比較器對象就按照比較器的順序排序。

  【6】出隊,優先順序最高的元素在堆頂(彈出堆頂元素),彈出前比較兩個子節點再進行堆化(自上而下)。

  【7】應用場景:1.業務辦理排隊叫號,VIP客戶插隊;2.電商搶購活動,會員級別高的用戶優先搶購到商品;


您的分享是我們最大的動力!

-Advertisement-
Play Games
更多相關文章
  • 這裡給大家分享我在網上總結出來的一些知識,希望對大家有所幫助 方法一 adb connect連接調試 前提條件: 電腦已安裝adb工具 手機和電腦連接的同一個WIFI CMD進入到adb工具所在目錄,可以使用HBuilder自帶adb,如:D:\Tools\HBuilderX\plugins\lau ...
  • 前言 從2015年左右開始,Google、Baidu、Facebook 等互聯網巨頭,不謀而合地開始大力推行 HTTPS, 國內外的大型互聯網公司很多也都已經啟用了全站 HTTPS 為鼓勵全球網站的 HTTPS 實現,一些互聯網公司都提出了自己的要求: 1)Google 已調整搜索引擎演算法,讓採用 ...
  • 就像我們經常所說的:沒有最好的架構,只有最合適的架構。一個好的架構師,可以根據具體的需求、所擁有的資源等因素綜合考慮而設計出最優的架構方案。特別是現在,業務的飛速變化、數據無處不在等這些因素的影響下,技術和框架也需要在變化的過程中不斷地打磨和提升以適應新的業務需要。可能當時是最好的架構,但是後來我們... ...
  • 關於架構師的成長之路,還存在著一個誤區,就是把架構師預設為軟體架構師。因為今天我們所遇到的架構師,大多數都是圍繞著軟體研發。事實上這個認識有一定的片面性。誠然,現今我們所構建的系統都是軟體系統,但是在實際的工作過程中,隨著信息技術在深度和廣度上的快速發展,除了軟體研發以外,測試、網路、安全、配置、系... ...
  • 享元設計模式(Flyweight Design Pattern)通過共用技術實現相同或相似對象的重用,節省記憶體,前提是享元對象是不可變對象。 ...
  • SynchronousQueue介紹 【1】SynchronousQueue是一個沒有數據緩衝的BlockingQueue,生產者線程對其的插入操作put必須等待消費者的移除操作take。 【2】如圖所示,SynchronousQueue 最大的不同之處在於,它的容量為 0,所以沒有一個地方來暫存元 ...
  • 1.數字的簡單運算 常用運算符 +, -, *, /, %, //,** = 就是賦值運算符,在變數介紹中已提及過,a=13; 這裡要說下賦值運算符的參數運算, +=, -=, *=, /=, //=, %= a += b --> a = a + b 參數賦值可以使代碼更整潔,可讀性更強 b,kb, ...
  • 前言 嗨嘍~大家好呀,這裡是魔王吶 ! 閑的無聊的得我又來倒騰代碼了~ 今天給大家分享得是——122萬人的生活工作和死亡數據分析 準備好了嘛~現在開始發車嘍!! @TOC 所需素材 獲取素材點擊 代碼 import pandas as pd df = pd.read_csv('.\data\AgeD ...
一周排行
    -Advertisement-
    Play Games
  • 移動開發(一):使用.NET MAUI開發第一個安卓APP 對於工作多年的C#程式員來說,近來想嘗試開發一款安卓APP,考慮了很久最終選擇使用.NET MAUI這個微軟官方的框架來嘗試體驗開發安卓APP,畢竟是使用Visual Studio開發工具,使用起來也比較的順手,結合微軟官方的教程進行了安卓 ...
  • 前言 QuestPDF 是一個開源 .NET 庫,用於生成 PDF 文檔。使用了C# Fluent API方式可簡化開發、減少錯誤並提高工作效率。利用它可以輕鬆生成 PDF 報告、發票、導出文件等。 項目介紹 QuestPDF 是一個革命性的開源 .NET 庫,它徹底改變了我們生成 PDF 文檔的方 ...
  • 項目地址 項目後端地址: https://github.com/ZyPLJ/ZYTteeHole 項目前端頁面地址: ZyPLJ/TreeHoleVue (github.com) https://github.com/ZyPLJ/TreeHoleVue 目前項目測試訪問地址: http://tree ...
  • 話不多說,直接開乾 一.下載 1.官方鏈接下載: https://www.microsoft.com/zh-cn/sql-server/sql-server-downloads 2.在下載目錄中找到下麵這個小的安裝包 SQL2022-SSEI-Dev.exe,運行開始下載SQL server; 二. ...
  • 前言 隨著物聯網(IoT)技術的迅猛發展,MQTT(消息隊列遙測傳輸)協議憑藉其輕量級和高效性,已成為眾多物聯網應用的首選通信標準。 MQTTnet 作為一個高性能的 .NET 開源庫,為 .NET 平臺上的 MQTT 客戶端與伺服器開發提供了強大的支持。 本文將全面介紹 MQTTnet 的核心功能 ...
  • Serilog支持多種接收器用於日誌存儲,增強器用於添加屬性,LogContext管理動態屬性,支持多種輸出格式包括純文本、JSON及ExpressionTemplate。還提供了自定義格式化選項,適用於不同需求。 ...
  • 目錄簡介獲取 HTML 文檔解析 HTML 文檔測試參考文章 簡介 動態內容網站使用 JavaScript 腳本動態檢索和渲染數據,爬取信息時需要模擬瀏覽器行為,否則獲取到的源碼基本是空的。 本文使用的爬取步驟如下: 使用 Selenium 獲取渲染後的 HTML 文檔 使用 HtmlAgility ...
  • 1.前言 什麼是熱更新 游戲或者軟體更新時,無需重新下載客戶端進行安裝,而是在應用程式啟動的情況下,在內部進行資源或者代碼更新 Unity目前常用熱更新解決方案 HybridCLR,Xlua,ILRuntime等 Unity目前常用資源管理解決方案 AssetBundles,Addressable, ...
  • 本文章主要是在C# ASP.NET Core Web API框架實現向手機發送驗證碼簡訊功能。這裡我選擇是一個互億無線簡訊驗證碼平臺,其實像阿裡雲,騰訊雲上面也可以。 首先我們先去 互億無線 https://www.ihuyi.com/api/sms.html 去註冊一個賬號 註冊完成賬號後,它會送 ...
  • 通過以下方式可以高效,並保證數據同步的可靠性 1.API設計 使用RESTful設計,確保API端點明確,並使用適當的HTTP方法(如POST用於創建,PUT用於更新)。 設計清晰的請求和響應模型,以確保客戶端能夠理解預期格式。 2.數據驗證 在伺服器端進行嚴格的數據驗證,確保接收到的數據符合預期格 ...