JAVA集合類概覽

帶着問題來閱讀html

一、Java有哪些集合java

二、不一樣集合的應用場景分別是哪些編程

三、哪些實現類是線程安全的segmentfault

四、爲何Java集合不能存放基本類型數組

五、集合的fail-fast和fail-safe是什麼安全

Java集合概覽

Java經過Java Collections Framework(JCF)爲開發者提供了一系列集合接口和實現,所謂集合,就是多個Java對象的彙集。數據結構

學習過數據結構的同窗們對各種集合的定義確定不陌生,Java經過提供一系列的內置數據結構實現,爲開發者提升了開發的便利性,提高了程序的兼容性,下降了編程的複雜性。併發

圖片出自https://www.pdai.tech/md/java/collection/java-collection-all.html框架

Java集合包含兩個頂層接口:Collection和Map,Collection是主要保存對象的集合,Map是保存鍵值對的集合。學習

下面咱們對兩類集合的實現作簡單介紹。

Collection

Collection是Jdk1.2版本引入的接口,用於描述保存對象的集合,它的擴展接口有List、Queue、Set。

List

List以列表形式順序存取元素,保證元素的插入順序和存儲順序一致。

實現 線程安全
ArrayList 數組
LinkedList 雙向鏈表
CopyOnWriteArrayList 數組 是,使用CopyOnWrite保證線程安全
Vector 數組 是,使用Synchronized保證線程安全
Stack 數組 是,繼承Vector

Vector是Jdk1.0就引入的線程安全的列表實現,早於Collection接口設計,採用直接在方法上添加Synchronized來保證線程安全,Stack是繼承Vector實現的棧結構,因爲其線程安全的低效性,目前在實際環境均再也不推薦使用。

Queue

Queue是先進先出的結構,從隊尾加入元素,隊頭彈出元素。其子接口Deque爲雙端隊列,即兩頭均可以進出。

實現 線程安全
ArrayDeque 循環數組
LinkedList 鏈表
PriorityQueue
BlockingQueue BlockingQueue爲阻塞隊列的擴展接口

因爲BlockingQueue略爲複雜,更涉及到一些進階應用場景,留待後續講解。

Set

Set是不重複元素集合,元素中不包含相同元素,且一般狀況不保持元素插入順序

實現 線程安全
HashSet 哈希表 否,基於HashMap實現
TreeSet 紅黑樹 否,基於TreeMap實現
LinkedHashSet 哈希表+鏈表 否,基於LinkedHashMap實現
CopyOnWriteArraySet 數組 是,CopyOnWrite保證
ConcurrentSkipListSet 跳錶 是,基於ConcurrentSkipListMap實現

Map

Map用於存儲<Key, Value>鍵值對。

實現 線程安全
HashMap 哈希表+紅黑樹
TreeMap 紅黑樹
LinkedHashMap 哈希表+鏈表+紅黑樹
WeakHashMap 哈希表
ConcurrentHashMap 哈希表+紅黑樹 是,基於節點CAS和Synchronized實現
ConcurrentSkipListMap 跳錶

爲何Java集合不能存放基本類型

Java在1.2版本引入JCF框架,Java範型是在1.5版本引入,所以在泛型引入以前集合默認以Object做爲存儲類型。

以List爲例
List list = new ArrayList();
list.add(123); // 自動boxing
list.add("123");
int num = (int) list.get(0);
String str = (String) list.get(1);

顯而易見該方式存在缺陷,集合內能夠放入任何以Object爲基類的元素,而元素的獲取方沒法肯定元素的具體類型,容易出現類型轉換錯誤。在1.5版本引入範型之後,對集合接口進行規範,添加了範型參數,Java的泛型機制本質上仍是將具體類型擦除爲Object,所以泛型集合在初始化時,沒法將參數指定爲非Object派生的基本類型。

什麼是fail-fast和fail-safe

List<String> list = new ArrayList();
list.add("123");
list.add("456");

//(1) throw ConcurrentModificationException
for (String s : list) {
	list.remove(s);
}

//(2) 正常移除
Iterator<String> it = list.iterator();
while (it.hasNext()) {
	it.next();
	it.remove();
}

//(3) throw ConcurrentModificationException
new Thread(() -> {
	for (String s : list) {
		System.out.println(s);
		try {
			TimeUnit.SECONDS.sleep(1);
		} catch (InterruptedException ignore)
		{
		
		}
	}
}).start();
new Thread(() -> {list.add("789");}).start();

上面這段代碼,(1) (3) 會拋出ConcurrentModificationException,(2)能夠正常移除全部元素。首先了解一下ConcurrentModificationException,當對一個對象作出的併發修改不被容許時,將拋出這個異常。

This exception may be thrown by methods that have detected concurrent modification of an object when such modification is not permissible.

那麼(1)是單線程執行,(3)併發添加元素也並未對已有元素作修改,爲何也會觸發該異常呢。

ArrayList var1 = new ArrayList();
var1.add("123");
var1.add("456");
Iterator var2 = var1.iterator();
while(var2.hasNext()) {
     String var3 = (String)var2.next();
     var1.remove(var3);
}

對代碼(1)的class文件反編譯查看,發現foreach其實是經過Iterator作的迭代,迭代過程當中刪除是直接調用list.remove。咱們再進入到list.iterator方法探個究竟。

/** Returns an iterator over the elements in this list in proper sequence.
 *  The returned iterator is fail-fast. */
public Iterator<E> iterator() {
    return new Itr();
}

private class Itr implements Iterator<E> {
    ....
    int expectedModCount = modCount;
    ....
        
    final void checkForComodification() {
        if (modCount != expectedModCount)
            throw new ConcurrentModificationException();
    }
}

iterator方法會建立一個Itr對象,在其創時會複製modCount到expectedModCount,每進行迭代時都會判斷兩個值是否相同,若是不一樣則拋出ConcurrentModificationException。

再來看modCount是一個繼承自AbstractList的成員變量,用於記錄list被修改的次數,每當調用add/remove時,modCount都會加1。

// The number of times this list has been structurally modified.
protected transient int modCount = 0;

那麼問題就很明顯了,每當對list進行修改modCount都會改變,而foreach的iterator記錄的是迭代對象建立時刻的modCount值,接下來的迭代過程當中,因爲調用了list的修改方法,改變了其中modCount的值,致使modCount != expectedModCount,因而就拋出了異常。(3)代碼是相同的問題,再也不進行贅述。

* <p><a name="fail-fast">
* The iterators returned by this class's {@link #iterator() iterator} and
* {@link #listIterator(int) listIterator} methods are <em>fail-fast</em>:</a>
* if the list is structurally modified at any time after the iterator is
* created, in any way except through the iterator's own
* {@link ListIterator#remove() remove} or
* {@link ListIterator#add(Object) add} methods, the iterator will throw a
* {@link ConcurrentModificationException}.  Thus, in the face of
* concurrent modification, the iterator fails quickly and cleanly, rather
* than risking arbitrary, non-deterministic behavior at an undetermined
* time in the future.

在全部Java集合類中,直接位於java.util下除Vector、Stack、HashTable外,全部的集合都是fail-fast的,而在java.util.concurrent下的集合都是fail-safe的,便可以併發的遍歷和修改集合,具體實現由各自的線程安全機制保證。

爲何須要fail-fast

fail-fast意爲快速失敗,在非線程安全的集合應用場景中,併發對集合作的添加/刪除,可能致使另外一個正在遍歷集合的線程出現未知的錯誤如數組越界。所以非線程安全的集合實現引入fail-fast以此來快速中斷線程,避免引起未知的連鎖問題。

參考

相關文章
相關標籤/搜索