面試官:請說出線程安全的 ArrayList 有哪些,除了Vector

面試官:請說出線程安全的 ArrayList 有哪些,除了Vector

以下環境是 JDK 1.8

ArrayList 的初始容量

面試官:你看過 ArrayList 的源碼?

Python 小星:看過

面試官:那你說下ArrayList 的初始容量是多少?

Python 小星:10

面試官:你確定!?

......

1、ArrayList源碼 -- 構造器

面試官:請說出線程安全的 ArrayList 有哪些,除了Vector

從源碼裡我們可以看到,無參構造函數,容量初始化為 0,有參構造函數的初始容量自定義。

我們也可以做個測試驗證,我們通過反射獲取 elementData 的長度,即是 ArrayList 的容量

面試官:請說出線程安全的 ArrayList 有哪些,除了Vector

輸出結果:

面試官:請說出線程安全的 ArrayList 有哪些,除了Vector

思考哈:為什麼默認長度是 10 ?

hashmap 裡默認 16,是為了 hash 算法。@Python大星 認為固定長度的數組初始容量不需要考慮 hashmap 裡的hash衝突,取 10 可能是不大不小的值,然後一直引用下來。就像你說為什麼數組的下標都是從 0 開始,而不是從 1 開始,a [0] 就是偏移為 0 的位置。a [k] 就表示偏移 k 個元素類型大小的位置,少做一步減法,就一直被繼承下來,無論是 C語言、Java語言 或者 Python 語言。

知道的小夥伴歡迎在評論下留言,也許無形中幫助了很多迷茫的人。

ArrayList 是“動態數組”-- 擴容

1、“動態”體現在 ArrayList的自動擴容上

ArrayList 如何完成一次擴容?

場景:ArrayList 初始容量是 10 ,如果再 add 一個元素,會怎樣?

面試官:請說出線程安全的 ArrayList 有哪些,除了Vector

我們可以看到 JDK8 相比之前做了一點優化,使用了 >> 位運算

數組會按照 10 + 10 * 0.5 = 15 擴容(把原來的數組複製到另一個內存空間更大的數組中),擴容後再把指向原數的地址換到新數組。

面試官:請說出線程安全的 ArrayList 有哪些,除了Vector

ArrayList、LinkedList、Vector 的區別?

① Arraylist 和 Vector 是採用數組方式存儲數據,所以插入數據慢,查找有下標,所以查詢數據快

此數組元素數大於實際存儲的數據以便增加插入元素,都允許直接序號索引元素,但是插入數據要涉及到數組元素移動等內存操作,

② Vector 本身所有方法都是用 synchronized 修飾的,線程安全,所以性能上比 ArrayList 要差

③ LinkedList 使用雙向鏈表實現存儲

按序號索引數據需要進行向前或向後遍歷,查找較慢,但是插入數據時只需要記錄本項前後項即可,插入數據較快。

為什麼說 ArrayList 不是線程安全?

1、測試

面試官:請說出線程安全的 ArrayList 有哪些,除了Vector

輸出結果:999

可以看出和我們預期的不一致。



面試官:請說出線程安全的 ArrayList 有哪些,除了Vector

在 add 操作分 2 步 :

① 判斷 elementData 數組容量是否滿足需求

② 在 elementData 對應位置上設置值

在多個線程進行 add 操作時可能會導致 elementData 數組越界。

elementData [size++] = e 設置值的操作同樣會導致線程不安全。從這兒可以看出,這步操作也不是一個原子操作,線程不安全。

LinkedList

LinkedList 內部是雙向鏈表結構

面試官:請說出線程安全的 ArrayList 有哪些,除了Vector

面試官:LinkedList 為什麼說查找慢?它是怎麼查找的?

Python 小星:因為它是鏈表結構,從表頭開始遍歷,所以當查找元素在鏈表後面,會比較慢

面試官:好的。回去等通知!

廢話不多說,我們看下源碼

面試官:請說出線程安全的 ArrayList 有哪些,除了Vector

面試官:請說出線程安全的 ArrayList 有哪些,除了Vector

從第二張圖中我們可以看出:

鏈表中的 index 只是標記元素的相對於鏈表頭部(first 指向的)node 的個數 ,這樣在根據 index 查詢時,可以根據 index 和 size 的關係,提高查詢性能。當 index 大致在鏈表的前半部分時(index > 1)),從鏈表的首部開始遍歷顯然更快,而當 index 大致在鏈表的後半部分時(index > (size >> 1)),從鏈表的尾部開始遍歷顯然更快,這樣就使得查找次數從 n 次將為了 n/2 次,雖然查找算法的時間複雜度還是 O (n)。

我們都知道 LinkedList 是鏈表結構,那到底是單向鏈表還是雙向鏈表?

面試官:請說出線程安全的 ArrayList 有哪些,除了Vector

由上圖可以看出Linkedlist是雙向鏈表

為什麼說 Vector 過時了,棄用了?

摘選 stackoverflow 的回答

https://stackoverflow.com/questions/1386275/why-is-java-vector-and-stack-class-considered-obsolete-or-deprecated#comment12234699_1386288

首先需要說明,在 Java 8 中 ,官方並沒有棄用。

面試官:請說出線程安全的 ArrayList 有哪些,除了Vector

① Vector 對每個單獨的方法進行同步;

② 通常 我們想要同步整個操作序列。

參考 https://javaconceptoftheday.com/not-use-vector-class-code/

① 無需 vector 也能實現線程安全

可以使用 Collections 類中 synchronizedList 來實現線程安全的 ArrayList

② 線程安全的 Vector 非常耗時

Vector 類的所有方法均已同步。這使 Vector 對象線程上的每個操作都安全。但是,這很耗時。因為,您需要為Vecto r在對象上執行的每個操作獲取對象鎖。通常,我們需要一組操作而不是每個操作都同步。一次鎖定對象,為什麼每次操作都要一次又一次次地獲得鎖?這是耗時的,降低性能。

③ Vector 設計不好

Vector 結合 2 個功能,“可調整大小的數組” 和 “同步”。這使設計不佳,而應始終使用ArrayList類。您將擁有可調整大小的數組,每當您要使其同步時,可以使用 Collections 中 synchronizedList 來實現線程安全的 ArrayList。

除了 Vector ,還有哪些線程安全的 ArrayList ?

synchronizedList 和 CopyOnWriteArrayList


1、synchronizedList

① synchronizedList 的用法(適合對數據要求較高的情況)

面試官:請說出線程安全的 ArrayList 有哪些,除了Vector

SynchronizedList 的 add 方法

面試官:請說出線程安全的 ArrayList 有哪些,除了Vector

add 方法

我們可以看出,SynchronizedList 用 synchronized 同步的是代碼塊,而 vector 用synchronized 同步的是方法。

【1】SynchronizedList 有很好的擴展和兼容功能。他可以將所有的 List 的子類轉成線程安全的類;

【2】使用 SynchronizedList 的時候,進行遍歷時要手動進行同步處理。

② CopyOnWriteArrayList (適合讀多寫少的場景)

1、add方法

CopyOnWriteArrayList 中 add 方法的實現(向 CopyOnWriteArrayList 裡添加元素),可以發現在添加的時候是需要加鎖的,寫入時複製(CopyOnWrite),copy 一份新的數組進行相關的操作,在執行完修改操作後將原來集合指向新的集合來完成修改操作

面試官:請說出線程安全的 ArrayList 有哪些,除了Vector

add方法

2、get方法

讀的時候不需要加鎖,如果讀的時候有多個線程正在向 CopyOnWriteArrayList 添加數據,讀還是會讀到舊的數據,因為寫的時候不會鎖住舊的 CopyOnWriteArrayList。

面試官:請說出線程安全的 ArrayList 有哪些,除了Vector

get方法

CopyOnWriteArrayList 缺點:

【1】 內存佔有問題:很明顯,兩個數組同時駐紮在內存中,如果實際應用中,數據比較多,而且比較大的情況下,佔用內存會比較大,針對這個其實可以用 ConcurrentHashMap 來代替。

【2】 數據一致性:CopyOnWrite 容器只能保證數據的最終一致性,不能保證數據的實時一致性。所以如果你希望寫入的的數據,馬上能讀到,請不要使用 CopyOnWrite 容器


| 文


分享到:


相關文章: