LinkedList源碼和並發問題分析
1.LinkedList源碼分析
LinkedList的是基於鏈表實現的java集合類,通過index插入到指定位置的時候使用LinkedList效率要比ArrayList高,以下源碼分析是基於JDK1.8.
1.1 類的繼承結構
LinkedList類的繼承結構如如下所示:
從以上繼承結構圖中可以看到,最頂層接口為Iterable接口,實現此接口的類支持通過叠代器遍歷集合中的元素。其他相關接口如Collection、List、Queue、Deque分別為列表,隊列功能的相關接口,即此LinkedList支持列表、隊列的相關操作。Serializable接口為序列化標誌接口,即所有需要序列化的類都需要實現此接口。
1.2 LinkedList數據結構說明
首先我們來看下LinkedList中保存數據的內部類定義源碼如下:
private static class Node<E> { E item; Node<E> next; Node<E> prev; Node(Node<E> prev, E element, Node<E> next) { this.item = element; this.next = next; this.prev = prev; } }
通過以上定義可以看出來每個節點中除了保存元素的值之外還保存了前後節點的引用。即使用了鏈表的數據接口保存數據元素。數據結構如下圖所示:
在LinkedList類中,只是定義了兩個Node類型的屬性,定義如下:
transient Node<E> first;
transient Node<E> last;
這兩個屬性分別表示頭節點和尾節點,始終指向鏈表的第一個節點和最後一個節點。
1.3 關鍵方法源碼分析
當不指定index往LinkedList中添加元素的時候默認是在鏈表的尾部添加數據,源代碼如下
void linkLast(E e) { //保存鏈表插入之前的最後一個節點 final Node<E> l = last; //將新節點的preNode指向鏈表最後一個節點 final Node<E> newNode = new Node<>(l, e, null); last指向插入後的尾節點 last = newNode; if (l == null) //當第一次插入數據的時候,頭節點和尾節點指向同一個節點 first = newNode; else //插入之前的尾節點的nextNode指向新插入的節點 l.next = newNode; size++; modCount++; }
插入節點的詳細操作如上面代碼註釋。在次操作中其實last即尾節點是共享資源,當多個線程同時執行此方法的時候,其實會出現線程安全問題。具體的線程安全問題後面分析。
當指定index插入數據的時候,源代碼如下所示:
public void add(int index, E element) {
checkPositionIndex(index);
if (index == size)
linkLast(element);
else
linkBefore(element, node(index));
}
在指定index插入元素的時候會先查詢出index位置的元素,然後調用linkBefore將此元素插入到查詢到的元素的前一個位置。其中linkBefore源碼如下所示:
void linkBefore(E e, Node<E> succ) {
// assert succ != null;
final Node<E> pred = succ.prev;
//新建節點,並將preNode指向idnex-1節點
final Node<E> newNode = new Node<>(pred, e, succ);
//插入前的index節點的prev指向新節點
succ.prev = newNode;
if (pred == null)
first = newNode;
else
//index-1節點的nextNode指向新節點
pred.next = newNode;
size++;
modCount++;
}
在指定節點插入的方式如上註釋所示。此操作中共享資源是插入之前index節點。同樣會出現並發安全問題,下面對此問題進行分析。
2.LinkedList並發插入時節點覆蓋的問題
在指定index插入或者addLast的時候都是在鏈表的尾部插入數據,當並發插入的時候如果出現以下執行順序的時候,會出現插入的數據被覆蓋的問題。
當多個線程同時獲取到相同的尾節點的時候,然後多個線程同時在此尾節點後面插入數據的時候會出現數據覆蓋的問題。
LinkedList源碼和並發問題分析