帶着問題來閱讀html
一、Java有哪些集合java
二、不一樣集合的應用場景分別是哪些編程
三、哪些實現類是線程安全的segmentfault
四、爲何Java集合不能存放基本類型數組
五、集合的fail-fast和fail-safe是什麼安全
Java經過Java Collections Framework(JCF)爲開發者提供了一系列集合接口和實現,所謂集合,就是多個Java對象的彙集。數據結構
學習過數據結構的同窗們對各種集合的定義確定不陌生,Java經過提供一系列的內置數據結構實現,爲開發者提升了開發的便利性,提高了程序的兼容性,下降了編程的複雜性。併發
圖片出自https://www.pdai.tech/md/java/collection/java-collection-all.html框架
Java集合包含兩個頂層接口:Collection和Map,Collection是主要保存對象的集合,Map是保存鍵值對的集合。學習
下面咱們對兩類集合的實現作簡單介紹。
Collection是Jdk1.2版本引入的接口,用於描述保存對象的集合,它的擴展接口有List、Queue、Set。
List以列表形式順序存取元素,保證元素的插入順序和存儲順序一致。
實現 | 線程安全 | |
---|---|---|
ArrayList | 數組 | 否 |
LinkedList | 雙向鏈表 | 否 |
CopyOnWriteArrayList | 數組 | 是,使用CopyOnWrite保證線程安全 |
Vector | 數組 | 是,使用Synchronized保證線程安全 |
Stack | 數組 | 是,繼承Vector |
Vector是Jdk1.0就引入的線程安全的列表實現,早於Collection接口設計,採用直接在方法上添加Synchronized來保證線程安全,Stack是繼承Vector實現的棧結構,因爲其線程安全的低效性,目前在實際環境均再也不推薦使用。
Queue是先進先出的結構,從隊尾加入元素,隊頭彈出元素。其子接口Deque爲雙端隊列,即兩頭均可以進出。
實現 | 線程安全 | |
---|---|---|
ArrayDeque | 循環數組 | 否 |
LinkedList | 鏈表 | 否 |
PriorityQueue | 堆 | 否 |
BlockingQueue | BlockingQueue爲阻塞隊列的擴展接口 | 是 |
因爲BlockingQueue略爲複雜,更涉及到一些進階應用場景,留待後續講解。
Set是不重複元素集合,元素中不包含相同元素,且一般狀況不保持元素插入順序
實現 | 線程安全 | |
---|---|---|
HashSet | 哈希表 | 否,基於HashMap實現 |
TreeSet | 紅黑樹 | 否,基於TreeMap實現 |
LinkedHashSet | 哈希表+鏈表 | 否,基於LinkedHashMap實現 |
CopyOnWriteArraySet | 數組 | 是,CopyOnWrite保證 |
ConcurrentSkipListSet | 跳錶 | 是,基於ConcurrentSkipListMap實現 |
Map用於存儲<Key, Value>鍵值對。
實現 | 線程安全 | |
---|---|---|
HashMap | 哈希表+紅黑樹 | 否 |
TreeMap | 紅黑樹 | 否 |
LinkedHashMap | 哈希表+鏈表+紅黑樹 | 否 |
WeakHashMap | 哈希表 | 否 |
ConcurrentHashMap | 哈希表+紅黑樹 | 是,基於節點CAS和Synchronized實現 |
ConcurrentSkipListMap | 跳錶 | 是 |
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派生的基本類型。
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以此來快速中斷線程,避免引起未知的連鎖問題。