死磕 java集合之LinkedBlockingQueue源碼分析

来源:https://www.cnblogs.com/tong-yuan/archive/2019/04/22/LinkedBlockingQueue.html
-Advertisement-
Play Games

LinkedBlockingQueue的實現方式? LinkedBlockingQueue是有界的還是無界的隊列? LinkedBlockingQueue相比ArrayBlockingQueue有什麼改進? ...


問題

(1)LinkedBlockingQueue的實現方式?

(2)LinkedBlockingQueue是有界的還是無界的隊列?

(3)LinkedBlockingQueue相比ArrayBlockingQueue有什麼改進?

簡介

LinkedBlockingQueue是java併發包下一個以單鏈表實現的阻塞隊列,它是線程安全的,至於它是不是有界的,請看下麵的分析。

源碼分析

主要屬性

// 容量
private final int capacity;

// 元素數量
private final AtomicInteger count = new AtomicInteger();

// 鏈表頭
transient Node<E> head;

// 鏈表尾
private transient Node<E> last;

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

// notEmpty條件
// 當隊列無元素時,take鎖會阻塞在notEmpty條件上,等待其它線程喚醒
private final Condition notEmpty = takeLock.newCondition();

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

// notFull條件
// 當隊列滿了時,put鎖會會阻塞在notFull上,等待其它線程喚醒
private final Condition notFull = putLock.newCondition();

(1)capacity,有容量,可以理解為LinkedBlockingQueue是有界隊列

(2)head, last,鏈表頭、鏈表尾指針

(3)takeLock,notEmpty,take鎖及其對應的條件

(4)putLock, notFull,put鎖及其對應的條件

(5)入隊、出隊使用兩個不同的鎖控制,鎖分離,提高效率

內部類

static class Node<E> {
    E item;

    Node<E> next;

    Node(E x) { item = x; }
}

典型的單鏈表結構。

主要構造方法

public LinkedBlockingQueue() {
    // 如果沒傳容量,就使用最大int值初始化其容量
    this(Integer.MAX_VALUE);
}

public LinkedBlockingQueue(int capacity) {
    if (capacity <= 0) throw new IllegalArgumentException();
    this.capacity = capacity;
    // 初始化head和last指針為空值節點
    last = head = new Node<E>(null);
}

入隊

入隊同樣有四個方法,我們這裡只分析最重要的一個,put(E e)方法:

public void put(E e) throws InterruptedException {
    // 不允許null元素
    if (e == null) throw new NullPointerException();
    int c = -1;
    // 新建一個節點
    Node<E> node = new Node<E>(e);
    final ReentrantLock putLock = this.putLock;
    final AtomicInteger count = this.count;
    // 使用put鎖加鎖
    putLock.lockInterruptibly();
    try {
        // 如果隊列滿了,就阻塞在notFull條件上
        // 等待被其它線程喚醒
        while (count.get() == capacity) {
            notFull.await();
        }
        // 隊列不滿了,就入隊
        enqueue(node);
        // 隊列長度加1
        c = count.getAndIncrement();
        // 如果現隊列長度如果小於容量
        // 就再喚醒一個阻塞在notFull條件上的線程
        // 這裡為啥要喚醒一下呢?
        // 因為可能有很多線程阻塞在notFull這個條件上的
        // 而取元素時只有取之前隊列是滿的才會喚醒notFull
        // 為什麼隊列滿的才喚醒notFull呢?
        // 因為喚醒是需要加putLock的,這是為了減少鎖的次數
        // 所以,這裡索性在放完元素就檢測一下,未滿就喚醒其它notFull上的線程
        // 說白了,這也是鎖分離帶來的代價
        if (c + 1 < capacity)
            notFull.signal();
    } finally {
        // 釋放鎖
        putLock.unlock();
    }
    // 如果原隊列長度為0,現在加了一個元素後立即喚醒notEmpty條件
    if (c == 0)
        signalNotEmpty();
}

private void enqueue(Node<E> node) {
    // 直接加到last後面
    last = last.next = node;
}    

private void signalNotEmpty() {
    final ReentrantLock takeLock = this.takeLock;
    // 加take鎖
    takeLock.lock();
    try {
        // 喚醒notEmpty條件
        notEmpty.signal();
    } finally {
        // 解鎖
        takeLock.unlock();
    }
}

(1)使用putLock加鎖;

(2)如果隊列滿了就阻塞在notFull條件上;

(3)否則就入隊;

(4)如果入隊後元素數量小於容量,喚醒其它阻塞在notFull條件上的線程;

(5)釋放鎖;

(6)如果放元素之前隊列長度為0,就喚醒notEmpty條件;

出隊

出隊同樣也有四個方法,我們這裡只分析最重要的那一個,take()方法:

public E take() throws InterruptedException {
    E x;
    int c = -1;
    final AtomicInteger count = this.count;
    final ReentrantLock takeLock = this.takeLock;
    // 使用takeLock加鎖
    takeLock.lockInterruptibly();
    try {
        // 如果隊列無元素,則阻塞在notEmpty條件上
        while (count.get() == 0) {
            notEmpty.await();
        }
        // 否則,出隊
        x = dequeue();
        // 獲取出隊前隊列的長度
        c = count.getAndDecrement();
        // 如果取之前隊列長度大於1,則喚醒notEmpty
        if (c > 1)
            notEmpty.signal();
    } finally {
        // 釋放鎖
        takeLock.unlock();
    }
    // 如果取之前隊列長度等於容量
    // 則喚醒notFull
    if (c == capacity)
        signalNotFull();
    return x;
}

private E dequeue() {
    // head節點本身是不存儲任何元素的
    // 這裡把head刪除,並把head下一個節點作為新的值
    // 並把其值置空,返回原來的值
    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;
}

private void signalNotFull() {
    final ReentrantLock putLock = this.putLock;
    putLock.lock();
    try {
        // 喚醒notFull
        notFull.signal();
    } finally {
        putLock.unlock();
    }
}

(1)使用takeLock加鎖;

(2)如果隊列空了就阻塞在notEmpty條件上;

(3)否則就出隊;

(4)如果出隊前元素數量大於1,喚醒其它阻塞在notEmpty條件上的線程;

(5)釋放鎖;

(6)如果取元素之前隊列長度等於容量,就喚醒notFull條件;

總結

(1)LinkedBlockingQueue採用單鏈表的形式實現;

(2)LinkedBlockingQueue採用兩把鎖的鎖分離技術實現入隊出隊互不阻塞;

(3)LinkedBlockingQueue是有界隊列,不傳入容量時預設為最大int值;

彩蛋

(1)LinkedBlockingQueue與ArrayBlockingQueue對比?

a)後者入隊出隊採用一把鎖,導致入隊出隊相互阻塞,效率低下;

b)前才入隊出隊採用兩把鎖,入隊出隊互不幹擾,效率較高;

c)二者都是有界隊列,如果長度相等且出隊速度跟不上入隊速度,都會導致大量線程阻塞;

d)前者如果初始化不傳入初始容量,則使用最大int值,如果出隊速度跟不上入隊速度,會導致隊列特別長,占用大量記憶體;


歡迎關註我的公眾號“彤哥讀源碼”,查看更多源碼系列文章, 與彤哥一起暢游源碼的海洋。

qrcode


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

-Advertisement-
Play Games
更多相關文章
  • java.util.logging.Logger——java 中提供的日誌類 實際開發 90% 都是使用 log4j 記錄日誌,而 Log4j 底層就是 java.util.logging.Logger 實現的 Log4j 是一個日誌輸出框架,就是用於輸出日誌的。Mybatis 的日誌輸出是通過 L ...
  • 1.cd 到指定目錄 2.運行命令 python 3之前 python 3+ 3.運行後: 4.在瀏覽器輸入 http://localhost:8888/. 測試,然後就可以瀏覽網頁 ...
  • 閱讀目錄 一、模塊和包 模塊(module)的概念: 在電腦程式的開發過程中,隨著程式代碼越寫越多,在一個文件里代碼會越來越長,越來越不容易維護。 為了編寫可維護的代碼,我們把很多函數分組,分別放到不同的文件里,這樣,每個文件包含的代碼就相對較少,很多編程語言都採用這種組織代碼的方式。在Pytho ...
  • 新聞 "Ionide試驗版本" "FSharp路線圖介紹" "Blazor官方預覽" ".NET Framework 4.8發佈" ".NET Core 3 Preview 4發佈" "需要來自FSharp.Data.SqlClient用戶的反饋" "Fable.React 5發佈" "Though ...
  • 一、介面思想 建立關聯的橋梁,方便管理代碼 介面思想提現:為類拓展功能 介面類:用來定義功能的類,為繼承它的子類提供功能的。 該類的功能方法一般不需要有實現體,實現體有繼承它的子類自己去實現。 介面類:用來定義功能的類,為繼承它的子類提供功能的。 該類的功能方法一般不需要有實現體,實現體有繼承它的子 ...
  • volatile是java虛擬機提供的輕量級的同步機制 JMM(Java記憶體模型)是圍繞著併發編程中原子性、可見性、有序性這三個特征來建立的 原子性:一個操作或多個操作要麼全部執行完成且執行過程不被中斷,要麼就不執行。 可見性:當多個線程同時訪問同一個變數時,一個線程修改了這個變數的值,其他線程能夠 ...
  • 包_繼承 1.包 包(package) 用於管理程式中的類,主要用於解決類的同名問題。包可以看成目錄。 包的作用: 【1】防止命名衝突 【2】允許類組成一個單元模塊,便於管理 【3】更好的保護類、屬性和方法 1.1定義包 package用於定義包,形如:package 路徑(包名) 必須寫到源文件的 ...
  • 1.包(package) 包(package) 用於管理程式中的類,主要用於解決類的同名問題。包也可以看成一個目錄。 包的作用 [1] 防止命名衝突。 [2] 允許類組成一個單元(模塊),便於管理和維護。 [3] 更好的保護類、屬性和方法 。 1.1 如何定義包 使用package進行定義,應放在源 ...
一周排行
    -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.數據驗證 在伺服器端進行嚴格的數據驗證,確保接收到的數據符合預期格 ...