1. 程式人生 > >10分鐘搞定 Java 併發佇列好嗎?好的

10分鐘搞定 Java 併發佇列好嗎?好的

| **好看請贊,養成習慣** > - 你有一個思想,我有一個思想,我們交換後,一個人就有兩個思想 > > - If you can NOT explain it simply, you do NOT understand it well enough 現陸續將Demo程式碼和技術文章整理在一起 [Github實踐精選](https://github.com/FraserYu/learnings) ,方便大家閱讀檢視,本文同樣收錄在此,覺得不錯,還請Star --- ## 前言 如果按照用途與特性進行粗略的劃分,JUC 包中包含的工具大體可以分為 6 類: 1. 執行者與執行緒池 2. 併發佇列 3. 同步工具 4. 併發集合 5. 鎖 6. 原子變數 在[併發系列](https://dayarch.top/categories/Coding/Java-Concurrency/)中,主要講解了 `執行者與執行緒池`,`同步工具`,`鎖` , 在分析原始碼時,或多或少的提及到了「佇列」,佇列在 JUC 中也是多種多樣存在,所以本文就以「遠看」視角,幫助大家快速瞭解與區分這些看似「雜亂」的佇列 ## 併發佇列 Java 併發佇列按照實現方式來進行劃分可以分為 2 種: 1. 阻塞佇列 2. 非阻塞佇列 如果你已經看完併發系列鎖的實現,你已經能夠知道他們實現的區別: > 前者就是基於鎖實現的,後者則是基於 CAS 非阻塞演算法實現的 常見的佇列有下面這幾種: ![](https://img2020.cnblogs.com/other/1583165/202008/1583165-20200826104538512-1987311664.png) 瞬間懵逼?看到這個沒有人性的圖想直接走人? 客觀先別急,一會就柳暗花明了 當下你也許有個問題: **為什麼會有這麼多種佇列的存在**? > 鎖有應對各種情形的鎖,佇列也自然有應對各種情形的隊列了, 是不是也有點單一職責原則的意思呢? 所以我們要了解這些佇列到底是怎麼設計的?以及用在了哪些地方? 先來看下圖 ![](https://img2020.cnblogs.com/other/1583165/202008/1583165-20200826104540932-505470422.png) 如果你在 IDE 中開啟以上非阻塞佇列和阻塞佇列,檢視其實現方法,你就會發現,`阻塞佇列`較`非阻塞佇列` **額外支援兩種操作**: 1. **阻塞的插入** 當佇列滿時,佇列會阻塞插入元素的執行緒,直到佇列不滿 2. **阻塞的移除** 當佇列為空時,獲取元素的執行緒會阻塞,直到佇列變為非空 綜合說明入隊/出隊操作,看似雜亂的方法,用一個表格就能概括了 ![](https://img2020.cnblogs.com/other/1583165/202008/1583165-20200826104541542-1898492282.png) **丟擲異常** - 當佇列滿時,此時如果再向佇列中插入元素,會丟擲 IllegalStateException (這很好理解) - 當佇列空時,此時如果再從佇列中獲取元素,會丟擲 NoSuchElementException (這也很好理解) **返回特殊值** - 當向佇列插入元素時,會返回元素是否插入成功,成功則返回 true - 當從佇列移除元素時,如果沒有則返回 null **一直阻塞** - 當佇列滿時,如果**生產者執行緒**向佇列 put 元素,佇列會一直阻塞生產者執行緒,直到佇列可用或者響應中斷退出 - 當佇列為空時,如果**消費者執行緒** 從佇列裡面 take 元素,佇列會阻塞消費者執行緒,直到佇列不為空 關於阻塞,我們其實早在 [併發程式設計之等待通知機制](https://dayarch.top/p/waiting-notification-mechanism.html) 就已經充分說明過了,你還記得下面這張圖嗎?原理其實是一樣一樣滴 ![](https://img2020.cnblogs.com/other/1583165/202008/1583165-20200826104542437-1983822963.png) **超時退出** 和鎖一樣,因為有阻塞,為了靈活使用,就一定支援超時退出,阻塞時間達到超時時間,就會直接返回 至於為啥插入和移除這麼多種單詞表示形式,我也不知道,為了方便記憶,只需要記住阻塞的方法形式即可: > 單詞 `put` 和 `take` 字母 `t` 首位相連,一個放,一個拿 到這裡你應該對 Java 併發佇列有了個初步的認識了,原來看似雜亂的方法貌似也有了規律。接下來就到了瘋狂串知識點的時刻了,藉助前序章節的知識,分分鐘就理解全部隊列了 ![](https://img2020.cnblogs.com/other/1583165/202008/1583165-20200826104542811-267882694.gif) ### ArrayBlockingQueue 之前也說過,JDK中的命名還是很講究滴,一看這名字,底層就是陣列實現了,是否有界,那就看在構造的時候是否需要指定 capacity 值了 填鴨式的說明也容易忘,這些都是哪看到的呢?在所有佇列的 Java docs 的第一段,一句話就概括了該佇列的主要特性,所以強烈建議大家自己在看原始碼時,簡單**瞄一眼** docs 開頭,心中就有多半個數了 ![](https://img2020.cnblogs.com/other/1583165/202008/1583165-20200826104544814-1897811766.png) 在講 [Java AQS佇列同步器以及ReentrantLock的應用](https://dayarch.top/p/java-aqs-and-reentrantlock.html) 時我們介紹了公平鎖與非公平鎖的概念,ArrayBlockingQueue 也有同樣的概念,看它的構造方法,就有 ReentrantLock 來輔助實現 ```java public ArrayBlockingQueue(int capacity, boolean fair) { if (capacity <= 0) throw new IllegalArgumentException(); this.items = new Object[capacity]; lock = new ReentrantLock(fair); notEmpty = lock.newCondition(); notFull = lock.newCondition(); } ``` 預設情況下,依舊是不保證執行緒公平訪問佇列(公平與否是指阻塞的執行緒能否按照阻塞的先後順序訪問佇列,先阻塞線訪問,後阻塞後訪問) 到這我也要臨時問一個說過多次的面試送分題了: > 為什麼預設採用非公平鎖的方式?它較公平鎖方式有什麼好處,又可能帶來哪些問題? 知道了以上內容,結合上面表格中的方法,ArrayBlockingQueue 就可以輕鬆過關了 ![](https://img2020.cnblogs.com/other/1583165/202008/1583165-20200826104545136-1836728809.png) 和陣列相對的自然是連結串列了 ### LinkedBlockingQueue ![](https://img2020.cnblogs.com/other/1583165/202008/1583165-20200826104545398-889900613.png) LinkedBlockingQueue 也算是一個有界阻塞佇列 ,從下面的建構函式中你也可以看出,該佇列的預設和最大長度為 Integer.MAX_VALUE ,這也就 docs 說 optionally-bounded 的原因了 ```java public LinkedBlockingQueue() { this(Integer.MAX_VALUE); } public LinkedBlockingQueue(int capacity) { if (capacity <= 0) throw new IllegalArgumentException(); this.capacity = capacity; last = head =