夯實Java基礎(chǔ)系列19:一文搞懂Java集合類框架,以及常見面試題

本系列文章將整理到我在GitHub上的《Java面試指南》倉庫,更多精彩內(nèi)容請到我的倉庫里查看

https://github.com/h2pl/Java-Tutorial

喜歡的話麻煩點下Star哈

文章首發(fā)于我的個人博客:

www.how2playlife.com

本文參考 https://www.cnblogs.com/chenssy/p/3495238.html

在編寫java程序中,我們最常用的除了八種基本數(shù)據(jù)類型,String對象外還有一個集合類,在我們的的程序中到處充斥著集合類的身影!

image

java中集合大家族的成員實在是太豐富了,有常用的ArrayList、HashMap、HashSet,也有不常用的Stack、Queue,有線程安全的Vector、HashTable,也有線程不安全的LinkedList、TreeMap等等!

上面的圖展示了整個集合大家族的成員以及他們之間的關(guān)系。下面就上面的各個接口、基類做一些簡單的介紹(主要介紹各個集合的特點。區(qū)別)。

下面幾張圖更清晰地介紹了結(jié)合類接口間的關(guān)系:

Collections和Collection。
Arrays和Collections。

Collection的子接口

map的實現(xiàn)類

Collection接口

Collection接口是最基本的集合接口,它不提供直接的實現(xiàn),Java SDK提供的類都是繼承自Collection的“子接口”如List和Set。Collection所代表的是一種規(guī)則,它所包含的元素都必須遵循一條或者多條規(guī)則。如有些允許重復(fù)而有些則不能重復(fù)、有些必須要按照順序插入而有些則是散列,有些支持排序但是有些則不支持。

在Java中所有實現(xiàn)了Collection接口的類都必須提供兩套標(biāo)準(zhǔn)的構(gòu)造函數(shù),一個是無參,用于創(chuàng)建一個空的Collection,一個是帶有Collection參數(shù)的有參構(gòu)造函數(shù),用于創(chuàng)建一個新的Collection,這個新的Collection與傳入進(jìn)來的Collection具備相同的元素。
//要求實現(xiàn)基本的增刪改查方法,并且需要能夠轉(zhuǎn)換為數(shù)組類型

public class Collection接口 {
    class collect implements Collection {

        @Override
        public int size() {
            return 0;
        }

        @Override
        public boolean isEmpty() {
            return false;
        }

        @Override
        public boolean contains(Object o) {
            return false;
        }

        @Override
        public Iterator iterator() {
            return null;
        }

        @Override
        public Object[] toArray() {
            return new Object[0];
        }

        @Override
        public boolean add(Object o) {
            return false;
        }

        @Override
        public boolean remove(Object o) {
            return false;
        }

        @Override
        public boolean addAll(Collection c) {
            return false;
        }

        @Override
        public void clear() {

        }
//省略部分代碼  

        @Override
        public Object[] toArray(Object[] a) {
            return new Object[0];
        }
    }
}

List接口

List接口為Collection直接接口。List所代表的是有序的Collection,即它用某種特定的插入順序來維護(hù)元素順序。用戶可以對列表中每個元素的插入位置進(jìn)行精確地控制,同時可以根據(jù)元素的整數(shù)索引(在列表中的位置)訪問元素,并搜索列表中的元素。實現(xiàn)List接口的集合主要有:ArrayList、LinkedList、Vector、Stack。

2.1、ArrayList

ArrayList是一個動態(tài)數(shù)組,也是我們最常用的集合。它允許任何符合規(guī)則的元素插入甚至包括null。每一個ArrayList都有一個初始容量(10),該容量代表了數(shù)組的大小。隨著容器中的元素不斷增加,容器的大小也會隨著增加。在每次向容器中增加元素的同時都會進(jìn)行容量檢查,當(dāng)快溢出時,就會進(jìn)行擴(kuò)容操作。所以如果我們明確所插入元素的多少,最好指定一個初始容量值,避免過多的進(jìn)行擴(kuò)容操作而浪費時間、效率。

size、isEmpty、get、set、iterator 和 listIterator 操作都以固定時間運行。add 操作以分?jǐn)偟墓潭〞r間運行,也就是說,添加 n 個元素需要 O(n) 時間(由于要考慮到擴(kuò)容,所以這不只是添加元素會帶來分?jǐn)偣潭〞r間開銷那樣簡單)。

ArrayList擅長于隨機(jī)訪問。同時ArrayList是非同步的。

2.2、LinkedList

同樣實現(xiàn)List接口的LinkedList與ArrayList不同,ArrayList是一個動態(tài)數(shù)組,而LinkedList是一個雙向鏈表。所以它除了有ArrayList的基本操作方法外還額外提供了get,remove,insert方法在LinkedList的首部或尾部。

由于實現(xiàn)的方式不同,LinkedList不能隨機(jī)訪問,它所有的操作都是要按照雙重鏈表的需要執(zhí)行。在列表中索引的操作將從開頭或結(jié)尾遍歷列表(從靠近指定索引的一端)。這樣做的好處就是可以通過較低的代價在List中進(jìn)行插入和刪除操作。

與ArrayList一樣,LinkedList也是非同步的。如果多個線程同時訪問一個List,則必須自己實現(xiàn)訪問同步。一種解決方法是在創(chuàng)建List時構(gòu)造一個同步的List:
List list = Collections.synchronizedList(new LinkedList(...));

2.3、Vector
與ArrayList相似,但是Vector是同步的。所以說Vector是線程安全的動態(tài)數(shù)組。它的操作與ArrayList幾乎一樣。

2.4、Stack
Stack繼承自Vector,實現(xiàn)一個后進(jìn)先出的堆棧。Stack提供5個額外的方法使得Vector得以被當(dāng)作堆棧使用。基本的push和pop 方法,還有peek方法得到棧頂?shù)脑兀琫mpty方法測試堆棧是否為空,search方法檢測一個元素在堆棧中的位置。Stack剛創(chuàng)建后是空棧。。

public class List接口 {
    //下面是List的繼承關(guān)系,由于List接口規(guī)定了包括諸如索引查詢,迭代器的實現(xiàn),所以實現(xiàn)List接口的類都會有這些方法。
    //所以不管是ArrayList和LinkedList底層都可以使用數(shù)組操作,但一般不提供這樣外部調(diào)用方法。
    //    public interface Iterable<T>
//    public interface Collection<E> extends Iterable<E>
//    public interface List<E> extends Collection<E>
    class MyList implements List {

        @Override
        public int size() {
            return 0;
        }

        @Override
        public boolean isEmpty() {
            return false;
        }

        @Override
        public boolean contains(Object o) {
            return false;
        }

        @Override
        public Iterator iterator() {
            return null;
        }

        @Override
        public Object[] toArray() {
            return new Object[0];
        }

        @Override
        public boolean add(Object o) {
            return false;
        }

        @Override
        public boolean remove(Object o) {
            return false;
        }

        @Override
        public void clear() {

        }

       //省略部分代碼
       
        @Override
        public Object get(int index) {
            return null;
        }

        @Override
        public ListIterator listIterator() {
            return null;
        }

        @Override
        public ListIterator listIterator(int index) {
            return null;
        }

        @Override
        public List subList(int fromIndex, int toIndex) {
            return null;
        }

        @Override
        public Object[] toArray(Object[] a) {
            return new Object[0];
        }
    }
}

Set接口

Set是一種不包括重復(fù)元素的Collection。它維持它自己的內(nèi)部排序,所以隨機(jī)訪問沒有任何意義。與List一樣,它同樣運行null的存在但是僅有一個。由于Set接口的特殊性,所有傳入Set集合中的元素都必須不同,同時要注意任何可變對象,如果在對集合中元素進(jìn)行操作時,導(dǎo)致e1.equals(e2)==true,則必定會產(chǎn)生某些問題。實現(xiàn)了Set接口的集合有:EnumSet、HashSet、TreeSet。

3.1、EnumSet
是枚舉的專用Set。所有的元素都是枚舉類型。

3.2、HashSet
HashSet堪稱查詢速度最快的集合,因為其內(nèi)部是以HashCode來實現(xiàn)的。它內(nèi)部元素的順序是由哈希碼來決定的,所以它不保證set 的迭代順序;特別是它不保證該順序恒久不變。

public class Set接口 {
    // Set接口規(guī)定將set看成一個集合,并且使用和數(shù)組類似的增刪改查方式,同時提供iterator迭代器
    //    public interface Set<E> extends Collection<E>
    //    public interface Collection<E> extends Iterable<E>
    //    public interface Iterable<T>
    class MySet implements Set {

        @Override
        public int size() {
            return 0;
        }

        @Override
        public boolean isEmpty() {
            return false;
        }

        @Override
        public boolean contains(Object o) {
            return false;
        }

        @Override
        public Iterator iterator() {
            return null;
        }

        @Override
        public Object[] toArray() {
            return new Object[0];
        }

        @Override
        public boolean add(Object o) {
            return false;
        }

        @Override
        public boolean remove(Object o) {
            return false;
        }

        @Override
        public boolean addAll(Collection c) {
            return false;
        }

        @Override
        public void clear() {

        }

        @Override
        public boolean removeAll(Collection c) {
            return false;
        }

        @Override
        public boolean retainAll(Collection c) {
            return false;
        }

        @Override
        public boolean containsAll(Collection c) {
            return false;
        }

        @Override
        public Object[] toArray(Object[] a) {
            return new Object[0];
        }
    }
}

Map接口

Map與List、Set接口不同,它是由一系列鍵值對組成的集合,提供了key到Value的映射。同時它也沒有繼承Collection。在Map中它保證了key與value之間的一一對應(yīng)關(guān)系。也就是說一個key對應(yīng)一個value,所以它不能存在相同的key值,當(dāng)然value值可以相同。實現(xiàn)map的有:HashMap、TreeMap、HashTable、Properties、EnumMap。

4.1、HashMap
以哈希表數(shù)據(jù)結(jié)構(gòu)實現(xiàn),查找對象時通過哈希函數(shù)計算其位置,它是為快速查詢而設(shè)計的,其內(nèi)部定義了一個hash表數(shù)組(Entry[] table),元素會通過哈希轉(zhuǎn)換函數(shù)將元素的哈希地址轉(zhuǎn)換成數(shù)組中存放的索引,如果有沖突,則使用散列鏈表的形式將所有相同哈希地址的元素串起來,可能通過查看HashMap.Entry的源碼它是一個單鏈表結(jié)構(gòu)。

4.2、TreeMap
鍵以某種排序規(guī)則排序,內(nèi)部以red-black(紅-黑)樹數(shù)據(jù)結(jié)構(gòu)實現(xiàn),實現(xiàn)了SortedMap接口

4.3、HashTable
也是以哈希表數(shù)據(jù)結(jié)構(gòu)實現(xiàn)的,解決沖突時與HashMap也一樣也是采用了散列鏈表的形式,不過性能比HashMap要低

public class Map接口 {
    //Map接口是最上層接口,Map接口實現(xiàn)類必須實現(xiàn)put和get等哈希操作。
    //并且要提供keyset和values,以及entryset等查詢結(jié)構(gòu)。
    //public interface Map<K,V>
    class MyMap implements Map {

        @Override
        public int size() {
            return 0;
        }

        @Override
        public boolean isEmpty() {
            return false;
        }

        @Override
        public boolean containsKey(Object key) {
            return false;
        }

        @Override
        public boolean containsValue(Object value) {
            return false;
        }

        @Override
        public Object get(Object key) {
            return null;
        }

        @Override
        public Object put(Object key, Object value) {
            return null;
        }

        @Override
        public Object remove(Object key) {
            return null;
        }

        @Override
        public void putAll(Map m) {

        }

        @Override
        public void clear() {

        }

        @Override
        public Set keySet() {
            return null;
        }

        @Override
        public Collection values() {
            return null;
        }

        @Override
        public Set<Entry> entrySet() {
            return null;
        }
    }
}

Queue

隊列,它主要分為兩大類,一類是阻塞式隊列,隊列滿了以后再插入元素則會拋出異常,主要包括ArrayBlockQueue、PriorityBlockingQueue、LinkedBlockingQueue。另一種隊列則是雙端隊列,支持在頭、尾兩端插入和移除元素,主要包括:ArrayDeque、LinkedBlockingDeque、LinkedList。

public class Queue接口 {
    //queue接口是對隊列的一個實現(xiàn),需要提供隊列的進(jìn)隊出隊等方法。一般使用linkedlist作為實現(xiàn)類
    class MyQueue implements Queue {

        @Override
        public int size() {
            return 0;
        }

        @Override
        public boolean isEmpty() {
            return false;
        }

        @Override
        public boolean contains(Object o) {
            return false;
        }

        @Override
        public Iterator iterator() {
            return null;
        }

        @Override
        public Object[] toArray() {
            return new Object[0];
        }

        @Override
        public Object[] toArray(Object[] a) {
            return new Object[0];
        }

        @Override
        public boolean add(Object o) {
            return false;
        }

        @Override
        public boolean remove(Object o) {
            return false;
        }

        //省略部分代碼
        @Override
        public boolean offer(Object o) {
            return false;
        }

        @Override
        public Object remove() {
            return null;
        }

        @Override
        public Object poll() {
            return null;
        }

        @Override
        public Object element() {
            return null;
        }

        @Override
        public Object peek() {
            return null;
        }
    }
}

關(guān)于Java集合的小抄

這部分內(nèi)容轉(zhuǎn)自我偶像 江南白衣 的博客:http://calvin1978.blogcn.com/articles/collection.html
在盡可能短的篇幅里,將所有集合與并發(fā)集合的特征、實現(xiàn)方式、性能捋一遍。適合所有"精通Java",其實還不那么自信的人閱讀。

期望能不止用于面試時,平時選擇數(shù)據(jù)結(jié)構(gòu),也能考慮一下其成本與效率,不要看著API合適就用了。

List

ArrayList

以數(shù)組實現(xiàn)。節(jié)約空間,但數(shù)組有容量限制。超出限制時會增加50%容量,用System.arraycopy()復(fù)制到新的數(shù)組。因此最好能給出數(shù)組大小的預(yù)估值。默認(rèn)第一次插入元素時創(chuàng)建大小為10的數(shù)組。

按數(shù)組下標(biāo)訪問元素-get(i)、set(i,e) 的性能很高,這是數(shù)組的基本優(yōu)勢。

如果按下標(biāo)插入元素、刪除元素-add(i,e)、 remove(i)、remove(e),則要用System.arraycopy()來復(fù)制移動部分受影響的元素,性能就變差了。

越是前面的元素,修改時要移動的元素越多。直接在數(shù)組末尾加入元素-常用的add(e),刪除最后一個元素則無影響。

LinkedList

以雙向鏈表實現(xiàn)。鏈表無容量限制,但雙向鏈表本身使用了更多空間,每插入一個元素都要構(gòu)造一個額外的Node對象,也需要額外的鏈表指針操作。

按下標(biāo)訪問元素-get(i)、set(i,e) 要悲劇的部分遍歷鏈表將指針移動到位 (如果i>數(shù)組大小的一半,會從末尾移起)。

插入、刪除元素時修改前后節(jié)點的指針即可,不再需要復(fù)制移動。但還是要部分遍歷鏈表的指針才能移動到下標(biāo)所指的位置。

只有在鏈表兩頭的操作-add()、addFirst()、removeLast()或用iterator()上的remove()倒能省掉指針的移動。

Apache Commons 有個TreeNodeList,里面是棵二叉樹,可以快速移動指針到位。

CopyOnWriteArrayList

并發(fā)優(yōu)化的ArrayList。基于不可變對象策略,在修改時先復(fù)制出一個數(shù)組快照來修改,改好了,再讓內(nèi)部指針指向新數(shù)組。

因為對快照的修改對讀操作來說不可見,所以讀讀之間不互斥,讀寫之間也不互斥,只有寫寫之間要加鎖互斥。但復(fù)制快照的成本昂貴,典型的適合讀多寫少的場景。

雖然增加了addIfAbsent(e)方法,會遍歷數(shù)組來檢查元素是否已存在,性能可想像的不會太好。

遺憾

無論哪種實現(xiàn),按值返回下標(biāo)contains(e), indexOf(e), remove(e) 都需遍歷所有元素進(jìn)行比較,性能可想像的不會太好。

沒有按元素值排序的SortedList。

除了CopyOnWriteArrayList,再沒有其他線程安全又并發(fā)優(yōu)化的實現(xiàn)如ConcurrentLinkedList。湊合著用Set與Queue中的等價類時,會缺少一些List特有的方法如get(i)。如果更新頻率較高,或數(shù)組較大時,還是得用Collections.synchronizedList(list),對所有操作用同一把鎖來保證線程安全。

Map

HashMap

以Entry[]數(shù)組實現(xiàn)的哈希桶數(shù)組,用Key的哈希值取模桶數(shù)組的大小可得到數(shù)組下標(biāo)。

插入元素時,如果兩條Key落在同一個桶(比如哈希值1和17取模16后都屬于第一個哈希桶),我們稱之為哈希沖突。

JDK的做法是鏈表法,Entry用一個next屬性實現(xiàn)多個Entry以單向鏈表存放。查找哈希值為17的key時,先定位到哈希桶,然后鏈表遍歷桶里所有元素,逐個比較其Hash值然后key值。

在JDK8里,新增默認(rèn)為8的閾值,當(dāng)一個桶里的Entry超過閥值,就不以單向鏈表而以紅黑樹來存放以加快Key的查找速度。

當(dāng)然,最好還是桶里只有一個元素,不用去比較。所以默認(rèn)當(dāng)Entry數(shù)量達(dá)到桶數(shù)量的75%時,哈希沖突已比較嚴(yán)重,就會成倍擴(kuò)容桶數(shù)組,并重新分配所有原來的Entry。擴(kuò)容成本不低,所以也最好有個預(yù)估值。

取模用與操作(hash & (arrayLength-1))會比較快,所以數(shù)組的大小永遠(yuǎn)是2的N次方, 你隨便給一個初始值比如17會轉(zhuǎn)為32。默認(rèn)第一次放入元素時的初始值是16。

iterator()時順著哈希桶數(shù)組來遍歷,看起來是個亂序。

LinkedHashMap

擴(kuò)展HashMap,每個Entry增加雙向鏈表,號稱是最占內(nèi)存的數(shù)據(jù)結(jié)構(gòu)。

支持iterator()時按Entry的插入順序來排序(如果設(shè)置accessOrder屬性為true,則所有讀寫訪問都排序)。

插入時,Entry把自己加到Header Entry的前面去。如果所有讀寫訪問都要排序,還要把前后Entry的before/after拼接起來以在鏈表中刪除掉自己,所以此時讀操作也是線程不安全的了。

3 TreeMap

以紅黑樹實現(xiàn),紅黑樹又叫自平衡二叉樹:

對于任一節(jié)點而言,其到葉節(jié)點的每一條路徑都包含相同數(shù)目的黑結(jié)點。
上面的規(guī)定,使得樹的層數(shù)不會差的太遠(yuǎn),使得所有操作的復(fù)雜度不超過 O(lgn),但也使得插入,修改時要復(fù)雜的左旋右旋來保持樹的平衡。

支持iterator()時按Key值排序,可按實現(xiàn)了Comparable接口的Key的升序排序,或由傳入的Comparator控制。可想象的,在樹上插入/刪除元素的代價一定比HashMap的大。

支持SortedMap接口,如firstKey(),lastKey()取得最大最小的key,或sub(fromKey, toKey), tailMap(fromKey)剪取Map的某一段。

EnumMap

EnumMap的原理是,在構(gòu)造函數(shù)里要傳入枚舉類,那它就構(gòu)建一個與枚舉的所有值等大的數(shù)組,按Enum. ordinal()下標(biāo)來訪問數(shù)組。性能與內(nèi)存占用俱佳。

美中不足的是,因為要實現(xiàn)Map接口,而 V get(Object key)中key是Object而不是泛型K,所以安全起見,EnumMap每次訪問都要先對Key進(jìn)行類型判斷,在JMC里錄得不低的采樣命中頻率。

ConcurrentHashMap

并發(fā)優(yōu)化的HashMap。

在JDK5里的經(jīng)典設(shè)計,默認(rèn)16把寫鎖(可以設(shè)置更多),有效分散了阻塞的概率。數(shù)據(jù)結(jié)構(gòu)為Segment[],每個Segment一把鎖。Segment里面才是哈希桶數(shù)組。Key先算出它在哪個Segment里,再去算它在哪個哈希桶里。

也沒有讀鎖,因為put/remove動作是個原子動作(比如put的整個過程是一個對數(shù)組元素/Entry 指針的賦值操作),讀操作不會看到一個更新動作的中間狀態(tài)。

但在JDK8里,Segment[]的設(shè)計被拋棄了,改為精心設(shè)計的,只在需要鎖的時候加鎖。

支持ConcurrentMap接口,如putIfAbsent(key,value)與相反的replace(key,value)與以及實現(xiàn)CAS的replace(key, oldValue, newValue)。

ConcurrentSkipListMap

JDK6新增的并發(fā)優(yōu)化的SortedMap,以SkipList結(jié)構(gòu)實現(xiàn)。Concurrent包選用它是因為它支持基于CAS的無鎖算法,而紅黑樹則沒有好的無鎖算法。

原理上,可以想象為多個鏈表組成的N層樓,其中的元素從稀疏到密集,每個元素有往右與往下的指針。從第一層樓開始遍歷,如果右端的值比期望的大,那就往下走一層,繼續(xù)往前走。

典型的空間換時間。每次插入,都要決定在哪幾層插入,同時,要決定要不要多蓋一層樓。

它的size()同樣不能隨便調(diào),會遍歷來統(tǒng)計。

Set

所有Set幾乎都是內(nèi)部用一個Map來實現(xiàn), 因為Map里的KeySet就是一個Set,而value是假值,全部使用同一個Object即可。

Set的特征也繼承了那些內(nèi)部的Map實現(xiàn)的特征。

HashSet:內(nèi)部是HashMap。

LinkedHashSet:內(nèi)部是LinkedHashMap。

TreeSet:內(nèi)部是TreeMap的SortedSet。

ConcurrentSkipListSet:內(nèi)部是ConcurrentSkipListMap的并發(fā)優(yōu)化的SortedSet。

CopyOnWriteArraySet:內(nèi)部是CopyOnWriteArrayList的并發(fā)優(yōu)化的Set,利用其addIfAbsent()方法實現(xiàn)元素去重,如前所述該方法的性能很一般。

好像少了個ConcurrentHashSet,本來也該有一個內(nèi)部用ConcurrentHashMap的簡單實現(xiàn),但JDK偏偏沒提供。Jetty就自己簡單封了一個,Guava則直接用java.util.Collections.newSetFromMap(new ConcurrentHashMap()) 實現(xiàn)。

Queue

Queue是在兩端出入的List,所以也可以用數(shù)組或鏈表來實現(xiàn)。

普通隊列

LinkedList
是的,以雙向鏈表實現(xiàn)的LinkedList既是List,也是Queue。

ArrayDeque
以循環(huán)數(shù)組實現(xiàn)的雙向Queue。大小是2的倍數(shù),默認(rèn)是16。

為了支持FIFO,即從數(shù)組尾壓入元素(快),從數(shù)組頭取出元素(超慢),就不能再使用普通ArrayList的實現(xiàn)了,改為使用循環(huán)數(shù)組。

有隊頭隊尾兩個下標(biāo):彈出元素時,隊頭下標(biāo)遞增;加入元素時,隊尾下標(biāo)遞增。如果加入元素時已到數(shù)組空間的末尾,則將元素賦值到數(shù)組[0],同時隊尾下標(biāo)指向0,再插入下一個元素則賦值到數(shù)組[1],隊尾下標(biāo)指向1。如果隊尾的下標(biāo)追上隊頭,說明數(shù)組所有空間已用完,進(jìn)行雙倍的數(shù)組擴(kuò)容。

PriorityQueue

用平衡二叉最小堆實現(xiàn)的優(yōu)先級隊列,不再是FIFO,而是按元素實現(xiàn)的Comparable接口或傳入Comparator的比較結(jié)果來出隊,數(shù)值越小,優(yōu)先級越高,越先出隊。但是注意其iterator()的返回不會排序。

平衡最小二叉堆,用一個簡單的數(shù)組即可表達(dá),可以快速尋址,沒有指針什么的。最小的在queue[0] ,比如queue[4]的兩個孩子,會在queue[24+1] 和 queue[2(4+1)],即queue[9]和queue[10]。

入隊時,插入queue[size],然后二叉地往上比較調(diào)整堆。

出隊時,彈出queue[0],然后把queque[size]拿出來二叉地往下比較調(diào)整堆。

初始大小為11,空間不夠時自動50%擴(kuò)容。

線程安全的隊列

ConcurrentLinkedQueue/Deque
無界的并發(fā)優(yōu)化的Queue,基于鏈表,實現(xiàn)了依賴于CAS的無鎖算法。

ConcurrentLinkedQueue的結(jié)構(gòu)是單向鏈表和head/tail兩個指針,因為入隊時需要修改隊尾元素的next指針,以及修改tail指向新入隊的元素兩個CAS動作無法原子,所以需要的特殊的算法。

線程安全的阻塞隊列

BlockingQueue,一來如果隊列已空不用重復(fù)的查看是否有新數(shù)據(jù)而會阻塞在那里,二來隊列的長度受限,用以保證生產(chǎn)者與消費者的速度不會相差太遠(yuǎn)。當(dāng)入隊時隊列已滿,或出隊時隊列已空,不同函數(shù)的效果見下表

ArrayBlockingQueue
定長的并發(fā)優(yōu)化的BlockingQueue,也是基于循環(huán)數(shù)組實現(xiàn)。有一把公共的鎖與notFull、notEmpty兩個Condition管理隊列滿或空時的阻塞狀態(tài)。

LinkedBlockingQueue/Deque
可選定長的并發(fā)優(yōu)化的BlockingQueue,基于鏈表實現(xiàn),所以可以把長度設(shè)為Integer.MAX_VALUE成為無界無等待的。

利用鏈表的特征,分離了takeLock與putLock兩把鎖,繼續(xù)用notEmpty、notFull管理隊列滿或空時的阻塞狀態(tài)。

PriorityBlockingQueue
無界的PriorityQueue,也是基于數(shù)組存儲的二叉堆(見前)。一把公共的鎖實現(xiàn)線程安全。因為無界,空間不夠時會自動擴(kuò)容,所以入列時不會鎖,出列為空時才會鎖。

DelayQueue
內(nèi)部包含一個PriorityQueue,同樣是無界的,同樣是出列時才會鎖。一把公共的鎖實現(xiàn)線程安全。元素需實現(xiàn)Delayed接口,每次調(diào)用時需返回當(dāng)前離觸發(fā)時間還有多久,小于0表示該觸發(fā)了。

pull()時會用peek()查看隊頭的元素,檢查是否到達(dá)觸發(fā)時間。ScheduledThreadPoolExecutor用了類似的結(jié)構(gòu)。

同步隊列

SynchronousQueue同步隊列本身無容量,放入元素時,比如等待元素被另一條線程的消費者取走再返回。JDK線程池里用它。

JDK7還有個LinkedTransferQueue,在普通線程安全的BlockingQueue的基礎(chǔ)上,增加一個transfer(e) 函數(shù),效果與SynchronousQueue一樣。

參考文章

https://blog.csdn.net/zzw1531439090/article/details/87872424

https://blog.csdn.net/weixin_40374341/article/details/86496343

https://www.cnblogs.com/uodut/p/7067162.html

https://www.jb51.net/article/135672.htm

https://www.cnblogs.com/suiyue-/p/6052456.html

微信公眾號

Java技術(shù)江湖

如果大家想要實時關(guān)注我更新的文章以及分享的干貨的話,可以關(guān)注我的公眾號【Java技術(shù)江湖】一位阿里 Java 工程師的技術(shù)小站,作者黃小斜,專注 Java 相關(guān)技術(shù):SSM、SpringBoot、MySQL、分布式、中間件、集群、Linux、網(wǎng)絡(luò)、多線程,偶爾講點Docker、ELK,同時也分享技術(shù)干貨和學(xué)習(xí)經(jīng)驗,致力于Java全棧開發(fā)!

Java工程師必備學(xué)習(xí)資源: 一些Java工程師常用學(xué)習(xí)資源,關(guān)注公眾號后,后臺回復(fù)關(guān)鍵字 “Java” 即可免費無套路獲取。

我的公眾號

個人公眾號:黃小斜

作者是 985 碩士,螞蟻金服 JAVA 工程師,專注于 JAVA 后端技術(shù)棧:SpringBoot、MySQL、分布式、中間件、微服務(wù),同時也懂點投資理財,偶爾講點算法和計算機(jī)理論基礎(chǔ),堅持學(xué)習(xí)和寫作,相信終身學(xué)習(xí)的力量!

程序員3T技術(shù)學(xué)習(xí)資源: 一些程序員學(xué)習(xí)技術(shù)的資源大禮包,關(guān)注公眾號后,后臺回復(fù)關(guān)鍵字 “資料” 即可免費無套路獲取。

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 228,156評論 6 531
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 98,401評論 3 415
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事。” “怎么了?”我有些...
    開封第一講書人閱讀 176,069評論 0 373
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經(jīng)常有香客問我,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 62,873評論 1 309
  • 正文 為了忘掉前任,我火速辦了婚禮,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當(dāng)我...
    茶點故事閱讀 71,635評論 6 408
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 55,128評論 1 323
  • 那天,我揣著相機(jī)與錄音,去河邊找鬼。 笑死,一個胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 43,203評論 3 441
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 42,365評論 0 288
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 48,881評論 1 334
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 40,733評論 3 354
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 42,935評論 1 369
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 38,475評論 5 358
  • 正文 年R本政府宣布,位于F島的核電站,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 44,172評論 3 347
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 34,582評論 0 26
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 35,821評論 1 282
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機(jī)就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個月前我還...
    沈念sama閱讀 51,595評論 3 390
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 47,908評論 2 372

推薦閱讀更多精彩內(nèi)容