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

一文帶你掌握Java?LinkedBlockingQueue

 更新時間:2023年04月04日 10:31:19   作者:好學(xué)的康達姆機器人  
LinkedBlockingQueue?是一個可選有界阻塞隊列,這篇文章主要為大家詳細介紹了Java中LinkedBlockingQueue的實現(xiàn)原理與適用場景,感興趣的可以了解一下

開篇語

隊列在生活中隨處可見,醫(yī)院繳費需要排隊、做核酸需要排隊、汽車等紅綠燈需要排隊等等。

隊列是一個按照先來到就排在前面,后來到排在后面的數(shù)據(jù)結(jié)構(gòu),并且出隊的時候也是按照先來到先出隊。使用數(shù)組和鏈表進行實現(xiàn)。通常用于協(xié)調(diào)任務(wù)的執(zhí)行和數(shù)據(jù)的交換。

介紹

LinkedBlockingQueue 是一個可選有界阻塞隊列,有界指的是隊列存在一個最大容量;阻塞指的是如果隊列已經(jīng)滿了,想要往隊列繼續(xù)添加元素的話,那么這個操作將會被暫停,直到隊列中有空位才會繼續(xù)完成添加操作。如果隊列已經(jīng)為空,想要從隊列中獲取元素,那么這個操作將會被暫停,直接隊列中存在元素才會繼續(xù)完成獲取操作。

實現(xiàn)原理

LinkedBlockingQueue 內(nèi)部使用鏈表作為元素的存儲結(jié)構(gòu)。內(nèi)部使用了兩個鎖,分別使用于存操作和取操作。

執(zhí)行存取操作時,都必須先獲取鎖,才可以執(zhí)行存取操作,保證 LinkedBlockingQueue 是線程安全。

LinkedBlockingQueue 通過兩個 Condition 條件隊列,一個 notFull 條件,一個 notEmpty 條件。在對隊列進行插入元素操作時,判斷當前隊列已經(jīng)滿,則通過 notFull 條件將線程阻塞,直到其他線程通知該線程隊列可以繼續(xù)插入元素。在對隊列進行移除元素操作時,判斷當前隊列已經(jīng)空,則通過 notEmpty 條件阻塞線程,直到其他線程通過該線程可以繼續(xù)獲取元素。

這樣保證線程的存取操作不會出現(xiàn)錯誤。避免隊列在滿時,丟棄插入的元素;也避免在隊列空時取到一個 null 值。

構(gòu)造函數(shù)

public LinkedBlockingQueue() {
    this(Integer.MAX_VALUE);
}

public LinkedBlockingQueue(int capacity) {
    if (capacity <= 0) throw new IllegalArgumentException();
    this.capacity = capacity;
    last = head = new Node<E>(null);
}

無參構(gòu)造函數(shù)中,默認使用 Integer.MAX_VALUE 作為隊列的最大容量。

有參構(gòu)造函數(shù)中,可以自己指定隊列的最大容量,并且創(chuàng)建了頭節(jié)點和尾節(jié)點。那么 LinkedBlockingQueue 使用的是有頭單向鏈表

private final int capacity;

/** Current number of elements */
private final AtomicInteger count = new AtomicInteger();

transient Node<E> head;

private transient Node<E> last;

// 取鎖
private final ReentrantLock takeLock = new ReentrantLock();

private final Condition notEmpty = takeLock.newCondition();

// 存鎖
private final ReentrantLock putLock = new ReentrantLock();

private final Condition notFull = putLock.newCondition();

并且在對象初始化時,創(chuàng)建了兩個鎖,分別使用于存操作和取操作。創(chuàng)建了兩個條件隊列,分別用于隊列空和滿的情況。

插入函數(shù)

public void put(E e) throws InterruptedException {
    if (e == null) throw new NullPointerException();
    final int c;
    final Node<E> node = new Node<E>(e);
    final ReentrantLock putLock = this.putLock;
    final AtomicInteger count = this.count;
    putLock.lockInterruptibly();
    try {
        while (count.get() == capacity) {
            notFull.await();
        }
        enqueue(node);
        c = count.getAndIncrement();
        if (c + 1 < capacity)
            notFull.signal();
    } finally {
        putLock.unlock();
    }
    if (c == 0)
        signalNotEmpty();
}

1.獲取鎖

2.判斷當前隊列是否已經(jīng)滿了

  • 如果隊列已經(jīng)滿了,調(diào)用 notFull 條件隊列的 await() 方法,將該線程阻塞,暫停該線程的插入操作。避免內(nèi)部溢出的問題。
  • 如果沒有滿,則直接調(diào)用入隊函數(shù) enqueue 插入到隊列末尾。

3.檢查此時隊列是否已滿

如果未滿,則調(diào)用 notFull 條件隊列的 signal() 方法,喚醒被阻塞在 notFull 條件隊列的線程。

4.解鎖

  • 檢查插入元素前的隊列元素數(shù)量是否等于0
  • 等于 0,則調(diào)用 notEmpty 條件隊列的 signal() 方法,通知其隊列現(xiàn)在不為空,可以喚醒阻塞線程獲取元素。

為什么需要調(diào)用 notFull 條件隊列的 signal() 方法? 因為隊列取操作和存操作所使用的鎖是不一樣的,那么就說明,一個線程執(zhí)行存入操作時,其他線程是可以執(zhí)行取出操作的。我們來看下面這個例子:

  • 隊列總?cè)萘繛?5,當前元素數(shù)量為5。線程 A 獲取了存鎖,想要插入了元素。但是因為隊列容量已滿,釋放鎖,并且加入到條件隊列中,等待被喚醒。
  • 線程 B 獲取了存鎖,想要插入了元素。但是因為隊列容量已滿,釋放鎖,并且加入到條件隊列中,等待被喚醒。
  • 線程 C 獲取了取鎖,取出了元素 1。并且通過 notFull 的 signal 方法喚醒條件隊列中被阻塞的線程 A。線程 A 被喚醒后加入到同步隊列中,但是此時還沒有競爭到鎖。
  • 線程 D 獲取了取鎖,取出了元素 2。但是還沒有執(zhí)行到喚醒阻塞線程的代碼。
  • 線程 A 競爭到鎖,開始執(zhí)行插入元素操作。將元素插入之后,檢查到隊列元素數(shù)量為 4,小于隊列的總?cè)萘?,因此會?zhí)行 notFull 的 signal 方法喚醒條件隊列中被阻塞的線程 B。線程 B 被喚醒后加入到同步隊列中,開始競爭鎖。
  • 線程 B 競爭到鎖,開始執(zhí)行插入元素操作。將元素插入之后,檢查到隊列元素數(shù)量等于 5,不進行喚醒操作。

這樣做的目的是盡快的喚醒阻塞線程,可以更快的完成插入元素操作。因為線程存和取的操作相互之間并不是互斥的,而是獨立運行的,提高吞吐量。

獲取函數(shù)

public E take() throws InterruptedException {
    final E x;
    final int c;
    final AtomicInteger count = this.count;
    final ReentrantLock takeLock = this.takeLock;
    takeLock.lockInterruptibly();
    try {
        while (count.get() == 0) {
            notEmpty.await();
        }
        x = dequeue();
        c = count.getAndDecrement();
        if (c > 1)
            notEmpty.signal();
    } finally {
        takeLock.unlock();
    }
    if (c == capacity)
        signalNotFull();
    return x;
}

1.獲得取鎖

2.判斷當前隊列是否為空

  • 如果隊列沒有元素,調(diào)用 notEmpty 條件隊列的 await() 方法,將該線程阻塞,暫停該線程的獲取操作。避免獲取元素出錯。
  • 如果不為空,則直接調(diào)用出隊函數(shù) dequeue 移除隊列第一個元素,并返回給客戶端。

3.檢查此時隊列是否為空

如果不為空,則調(diào)用 notEmpty 條件隊列的 signal() 方法,喚醒被阻塞在 notEmpty 條件隊列的線程。

4.釋放鎖

5.檢查獲取元素前的隊列元素數(shù)量是否等于最大容量

等于最大容量,因為此時已經(jīng)取出一個元素,因此隊列處于未滿的狀態(tài),可以喚醒阻塞在 notFull 條件的線程,讓線程繼續(xù)插入元素。

步驟 3 的目的是盡快的喚醒阻塞線程,可以更快的完成取元素操作。提高吞吐量??梢試L試自己畫出流程圖。

入隊函數(shù)

private void enqueue(Node<E> node) {
    last = last.next = node;
}

入隊函數(shù)極其簡單,只要將最后一個元素的 next 指針指向當前元素即完成了插入操作。

出隊函數(shù)

private E dequeue() {
    // assert takeLock.isHeldByCurrentThread();
    // assert head.item == null;
    Node<E> h = head;
    Node<E> first = h.next;
    h.next = h; // help GC
    head = first;
    E x = first.item;
    first.item = null;
    return x;
}

我們前面說 LinkedBlockingQueue 使用的是有頭鏈表。頭節(jié)點只是作為一個標志,實際上并不是一個真正的元素。當獲取元素時,將頭節(jié)點的下一個節(jié)點作為頭節(jié)點,將原來的頭節(jié)點取消引用,被垃圾回收即可。

應(yīng)用場景

適用場景

LinkedBlockingQueue 和 ArrayBlockingQueue 一樣適用于多個線程之間需要共享數(shù)據(jù)、協(xié)調(diào)任務(wù)執(zhí)行的場景。因此可以總結(jié)出以下幾個應(yīng)用場景:

線程池:線程池是一個常見的并發(fā)編程模型,它通過線程池中的線程執(zhí)行任務(wù)。并且可以重復(fù)使用這些線程。在線程池中,可以使用 LinkedBlockingQueue 來存儲需要執(zhí)行的任務(wù),以此控制任務(wù)數(shù)量和執(zhí)行順序。當線程池中的線程執(zhí)行完任務(wù)之后,可以從 LinkedBlockingQueue 中取出下一個任務(wù)執(zhí)行。

生產(chǎn)者-消費者:在生產(chǎn)者-消費者模型中,生產(chǎn)者負責生產(chǎn)數(shù)據(jù),消費者負責對數(shù)據(jù)進行處理。在這種模式下,LinkedBlockingQueue 可以作為生產(chǎn)者與消費者之間的數(shù)據(jù)通道,保證線程安全和數(shù)據(jù)正確。

實際應(yīng)用場景

  • Nacos: Nacos 是一個動態(tài)服務(wù)發(fā)現(xiàn)、配置和服務(wù)管理平臺,它使用 LinkedBlockingQueue 來實現(xiàn)內(nèi)部的任務(wù)隊列。
  • Tomcat:從 Tomcat 7 開始,請求隊列默認使用了 LinkedBlockingQueue 實現(xiàn)。
  • Hystrix: 一個流行的容錯框架,其默認使用 LinkedBlockingQueue 作為請求隊列。

總結(jié)

LinkedBlockingQueue 和 ArrayBlockQueue 的對比:

  • 實現(xiàn)方式不同:LinkedBlockingQueue 是基于鏈表實現(xiàn)的隊列,而 ArrayBlockingQueue 是基于數(shù)組實現(xiàn)的隊列。
  • 插入和刪除元素的效率不同:LinkedBlockingQueue 的插入和刪除元素的效率較高,因為它采用了兩把鎖來控制讀寫操作;而 ArrayBlockingQueue 的插入和刪除元素的效率相對較低,因為它采用了一把鎖來控制讀寫操作。
  • 內(nèi)存占用不同:LinkedBlockingQueue 的內(nèi)存占用比 ArrayBlockingQueue 更高,因為鏈表結(jié)構(gòu)需要額外的指針存儲,而數(shù)組結(jié)構(gòu)則不需要。
  • 阻塞模式不同:ArrayBlockingQueue支持公平模式和非公平模式,可以控制入隊和出隊的順序;而 LinkedBlockingQueue 只支持非公平模式。

到此這篇關(guān)于一文帶你掌握Java LinkedBlockingQueue的文章就介紹到這了,更多相關(guān)Java LinkedBlockingQueue內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!

相關(guān)文章

  • Mybatis防止sql注入的實例

    Mybatis防止sql注入的實例

    本文通過實例給大家介紹了Mybatis防止sql注入的相關(guān)資料,非常不錯,具有參考借鑒價值,需要的朋友可以參考下
    2017-06-06
  • Java編寫實現(xiàn)多人聊天室

    Java編寫實現(xiàn)多人聊天室

    這篇文章主要為大家詳細介紹了Java編寫實現(xiàn)多人聊天室,文中示例代碼介紹的非常詳細,具有一定的參考價值,感興趣的小伙伴們可以參考一下
    2022-09-09
  • 最全面的JVM優(yōu)化經(jīng)驗總結(jié)

    最全面的JVM優(yōu)化經(jīng)驗總結(jié)

    這篇文章主要介紹了最全面的JVM優(yōu)化經(jīng)驗總結(jié),文中通過示例代碼介紹的非常詳細,對大家的學(xué)習或者工作具有一定的參考學(xué)習價值,,需要的朋友可以參考下
    2019-06-06
  • Java?EasyExcel實現(xiàn)合并相同內(nèi)容單元格與動態(tài)標題功能

    Java?EasyExcel實現(xiàn)合并相同內(nèi)容單元格與動態(tài)標題功能

    這篇文章主要為大家詳細介紹了Java?EasyExcel如何實現(xiàn)合并相同內(nèi)容單元格與動態(tài)標題功能,文中的示例代碼講解詳細,有需要的小伙伴可以參考下
    2023-12-12
  • Java?死鎖解決方案順序鎖和輪詢鎖

    Java?死鎖解決方案順序鎖和輪詢鎖

    這篇文章主要介紹了Java?死鎖解決方案順序鎖和輪詢鎖,文章圍繞主題展開詳細的內(nèi)容介紹,具有一定的參考價值,需要的小伙伴可以參考一下
    2022-05-05
  • 詳解Guava Cache本地緩存在Spring Boot應(yīng)用中的實踐

    詳解Guava Cache本地緩存在Spring Boot應(yīng)用中的實踐

    Guava Cache是一個全內(nèi)存的本地緩存實現(xiàn),本文將講述如何將 Guava Cache緩存應(yīng)用到 Spring Boot應(yīng)用中。具有一定的參考價值,感興趣的小伙伴們可以參考一下
    2019-01-01
  • SpringBoot+OCR?實現(xiàn)圖片文字識別

    SpringBoot+OCR?實現(xiàn)圖片文字識別

    本文主要介紹了SpringBoot+OCR 實現(xiàn)圖片文字識別,文中通過示例代碼介紹的非常詳細,具有一定的參考價值,感興趣的小伙伴們可以參考一下
    2021-12-12
  • Hibernate一級緩存和二級緩存詳解

    Hibernate一級緩存和二級緩存詳解

    今天小編就為大家分享一篇關(guān)于Hibernate一級緩存和二級緩存詳解,小編覺得內(nèi)容挺不錯的,現(xiàn)在分享給大家,具有很好的參考價值,需要的朋友一起跟隨小編來看看吧
    2019-03-03
  • java?并發(fā)線程個數(shù)的如何確定

    java?并發(fā)線程個數(shù)的如何確定

    這篇文章主要介紹了java?并發(fā)線程個數(shù)的如何確定,具有很好的參考價值,希望對大家有所幫助。如有錯誤或未考慮完全的地方,望不吝賜教
    2021-12-12
  • Springboot中的@Order如何使用

    Springboot中的@Order如何使用

    本文主要介紹了Springboot中的@Order如何使用,文中通過示例代碼介紹的非常詳細,具有一定的參考價值,感興趣的小伙伴們可以參考一下
    2022-02-02

最新評論