一、前言
最近在做項目的同時也在修復之前項目的一些Bug,所以忙得沒有時間看源代碼,今天都完成得差不多了,所以又開始源碼分析之路,也着筆記錄下ConcurrentSkipListMap的源碼的分析過程。
二、ConcurrentSkipListMap數據結構
抓住了數據結構,對於理解整個ConcurrentSkipListMap有很重要的作用,其實,通過源碼可知其數據結構如下。
說明:可以看到ConcurrentSkipListMap的數據結構使用的是跳表,每一個HeadIndex、Index結點都會包含一個對Node的引用,同一垂直方向上的Index、HeadIndex結點都包含了最底層的Node結點的引用。並且層級越高,該層級的結點(HeadIndex和Index)數越少。Node結點之間使用單鏈表結構。
三、ConcurrentSkipListMap源碼分析
3.1 類的繼承關系
public class ConcurrentSkipListMap<K,V> extends AbstractMap<K,V> implements ConcurrentNavigableMap<K,V>, Cloneable, Serializable {}
說明:ConcurrentSkipListMap繼承了AbstractMap抽象類,實現了ConcurrentNavigableMap接口,該接口定義了獲取某一部分集合的操作。實現了Cloneable接口,表示允許克隆。實現了Serializable接口,表示可被序列化。
3.2 類的內部類
ConcurrentSkipListMap包含了很多內部類,內部類的框架圖如下:
說明:其中,最為重要的類包括Index、HeadIndex、Node三個類。下面對這三個類進行逐一講解,其他的類,讀者有興趣可以自行分析。
① Index類
1. 類的屬性
static class Index<K,V> { final Node<K,V> node; final Index<K,V> down; volatile Index<K,V> right; // Unsafe mechanics private static final sun.misc.Unsafe UNSAFE; private static final long rightOffset; static { try { UNSAFE = sun.misc.Unsafe.getUnsafe(); Class<?> k = Index.class; rightOffset = UNSAFE.objectFieldOffset (k.getDeclaredField("right")); } catch (Exception e) { throw new Error(e); } } }
說明:可以看到,Index結點包括一個Node結點的引用,都是包含down域和right域,即對應數據結構中的Index結點。並且借助了反射來原子性的修改right域。
2. 類的構造函數

/** * Creates index node with given values. */ Index(Node<K,V> node, Index<K,V> down, Index<K,V> right) { this.node = node; this.down = down; this.right = right; }
說明:構造Index結點,確定Node引用,down域和right域。
3. 核心函數分析
3.1 link函數

final boolean link(Index<K,V> succ, Index<K,V> newSucc) { // 獲取Index結點的Node結點 Node<K,V> n = node; // 將newSucc結點的right域設置為succ newSucc.right = succ; // 結點的值不為空並且比較並交換當前Index結點的right域(將當前Index(this)結點的right域設置為newSucc) return n.value != null && casRight(succ, newSucc); }
說明:link方法用於在當前Index結點的后面插入一個Index結點,形成其right結點。並且插入的Index結點的right域為當前結點的right域。
3.2 unlink函數

final boolean unlink(Index<K,V> succ) { // 當前Index結點的Node結點的值不為空並且將當前Index結點的right設置為succ的right結點 return node.value != null && casRight(succ, succ.right); }
說明:unlink方法與link方法作用相反,其刪除當前Index結點的right結點,即將當前Index結點的right指向當前Index結點的right.right域。
② HeadIndex類
// 頭結點索引類 static final class HeadIndex<K,V> extends Index<K,V> { // 層級 final int level; // 構造函數 HeadIndex(Node<K,V> node, Index<K,V> down, Index<K,V> right, int level) { // 構造Index類 super(node, down, right); this.level = level; } }
說明:根據HeadIndex類可知其繼承自Index類,並且在Index類的基礎上添加了level域,表示當前的層級。
③ Node類
1. 類的屬性

static final class Node<K,V> { // 鍵 final K key; // 值 volatile Object value; // 下一個結點 volatile Node<K,V> next; // UNSAFE mechanics private static final sun.misc.Unsafe UNSAFE; // value域的偏移地址 private static final long valueOffset; // next域的偏移地址 private static final long nextOffset; static { try { UNSAFE = sun.misc.Unsafe.getUnsafe(); Class<?> k = Node.class; valueOffset = UNSAFE.objectFieldOffset (k.getDeclaredField("value")); nextOffset = UNSAFE.objectFieldOffset (k.getDeclaredField("next")); } catch (Exception e) { throw new Error(e); } } }
說明:Node類包含了key、value、next域,其是用來實際存放元素的結點,並且是使用單鏈表結構。同時,也使用了反射來原子性的修改value與和next域。
2. 類的構造函數

Node(K key, Object value, Node<K,V> next) { this.key = key; this.value = value; this.next = next; } /** * Creates a new marker node. A marker is distinguished by * having its value field point to itself. Marker nodes also * have null keys, a fact that is exploited in a few places, * but this doesn't distinguish markers from the base-level * header node (head.node), which also has a null key. */ // 用於建立標記結點,值為本身 Node(Node<K,V> next) { this.key = null; this.value = this; this.next = next; }
說明:Node類包含了兩種構造函數,分別表示正常的結點和marker標記結點,marker標記結點在刪除結點時被使用。
3. 類的核心函數
3.1 helpDelete函數

void helpDelete(Node<K,V> b, Node<K,V> f) { /* * Rechecking links and then doing only one of the * help-out stages per call tends to minimize CAS * interference among helping threads. */ if (f == next && this == b.next) { // f為當前結點的后繼並且b為當前結點的前驅 if (f == null || f.value != f) // f為空或者f的value不為本身,即沒有被標記 not already marked // 當前結點后添加一個marker結點,並且當前結點的后繼為marker,marker結點的后繼為f casNext(f, new Node<K,V>(f)); else // f不為空並且f的值為本身 // 設置b的next域為f的next域 b.casNext(this, f.next); } }
說明:刪除結點,在結點后面添加一個marker結點或者將結點和其后的marker結點從其前驅中斷開。
3.3 類的屬性

public class ConcurrentSkipListMap<K,V> extends AbstractMap<K,V> implements ConcurrentNavigableMap<K,V>, Cloneable, Serializable { // 版本序列號 private static final long serialVersionUID = -8627078645895051609L; // 基礎層的頭結點 private static final Object BASE_HEADER = new Object(); // 最頂層頭結點的索引 private transient volatile HeadIndex<K,V> head; // 比較器 final Comparator<? super K> comparator; // 鍵集合 private transient KeySet<K> keySet; // entry集合 private transient EntrySet<K,V> entrySet; // 值集合 private transient Values<V> values; // 降序鍵集合 private transient ConcurrentNavigableMap<K,V> descendingMap; // Unsafe mechanics private static final sun.misc.Unsafe UNSAFE; // head域的偏移量 private static final long headOffset; // Thread類的threadLocalRandomSecondarySeed的偏移量 private static final long SECONDARY; static { try { UNSAFE = sun.misc.Unsafe.getUnsafe(); Class<?> k = ConcurrentSkipListMap.class; headOffset = UNSAFE.objectFieldOffset (k.getDeclaredField("head")); Class<?> tk = Thread.class; SECONDARY = UNSAFE.objectFieldOffset (tk.getDeclaredField("threadLocalRandomSecondarySeed")); } catch (Exception e) { throw new Error(e); } } }
說明:ConcurrentSkipListMap包含了head屬性,表示跳表的頭結點,並且包含了一個比較器,值得注意的是,對於ConcurrentSkipListMap的使用,鍵必須能夠進行比較,如傳遞了比較器或者鍵本身就能夠進行比較。同時,也使用了反射來保證原子性的更新head域。
3.4 類的構造函數
1. ConcurrentSkipListMap()型構造函數

// 構造一個新的空映射,該映射按照鍵的自然順序進行排序 public ConcurrentSkipListMap() { // 比較器為空,那么鍵必須能夠比較(實現了Comparable接口) this.comparator = null; // 初始化相關的域 initialize(); }
說明:構造一個新的空映射,該映射按照鍵的自然順序進行排序,即鍵K必須實現了Comparable接口,否則,會報錯。
2. ConcurrentSkipListMap(Comparator<? super K>)型構造函數

// 構造一個新的空映射,該映射按照指定的比較器進行排序 public ConcurrentSkipListMap(Comparator<? super K> comparator) { // 初始化比較器 this.comparator = comparator; // 初始化相關的域 initialize(); }
說明:構造一個新的空映射,該映射按照指定的比較器進行排序
3. ConcurrentSkipListMap(Map<? extends K, ? extends V>)型構造函數

// 構造一個新映射,該映射所包含的映射關系與給定映射包含的映射關系相同,並按照鍵的自然順序進行排序 public ConcurrentSkipListMap(Map<? extends K, ? extends V> m) { // 比較器Wie空 this.comparator = null; // 初始化相關的域 initialize(); // 將m的所有元素添加至跳表 putAll(m); }
說明:構造一個新映射,該映射所包含的映射關系與給定映射包含的映射關系相同,並按照鍵的自然順序進行排序。
4. ConcurrentSkipListMap(SortedMap<K, ? extends V>)型構造函數

// 構造一個新映射,該映射所包含的映射關系與指定的有序映射包含的映射關系相同,使用的順序也相同 public ConcurrentSkipListMap(SortedMap<K, ? extends V> m) { // 獲取m的比較器 this.comparator = m.comparator(); // 初始化相關的域 initialize(); // 根據m的元素來構建跳表 buildFromSorted(m); }
說明:構造一個新映射,該映射所包含的映射關系與指定的有序映射包含的映射關系相同,使用的順序也相同。
3.5 核心函數分析
1. doPut函數

// 插入一個結點 private V doPut(K key, V value, boolean onlyIfAbsent) { Node<K,V> z; // added node if (key == null) // 鍵為空,拋出空異常 throw new NullPointerException(); // 比較器 Comparator<? super K> cmp = comparator; outer: for (;;) { // 無限循環 for (Node<K,V> b = findPredecessor(key, cmp), n = b.next;;) { // 找到先驅結點,n為當前結點 if (n != null) { // next域不為空 Object v; int c; // f為當前結點的后繼節點 Node<K,V> f = n.next; if (n != b.next) // 不一致,重試 // inconsistent read break; if ((v = n.value) == null) { // n結點已經被刪除 // n is deleted // 進行刪除 n.helpDelete(b, f); break; } if (b.value == null || v == n) // b結點已經被刪除 b is deleted break; if ((c = cpr(cmp, key, n.key)) > 0) { // key大於結點的key // b往后移動 b = n; // n往后移動 n = f; continue; } if (c == 0) { // 鍵相等 if (onlyIfAbsent || n.casValue(v, value)) { // 比較並交換值 @SuppressWarnings("unchecked") V vv = (V)v; return vv; } // 重試 break; // restart if lost race to replace value } // else c < 0; fall through } // 新生一個結點 z = new Node<K,V>(key, value, n); if (!b.casNext(n, z)) // 比較並交換next域 break; // restart if lost race to append to b // 成功,則跳出循環 break outer; } } // 隨機生成種子 int rnd = ThreadLocalRandom.nextSecondarySeed(); if ((rnd & 0x80000001) == 0) { // test highest and lowest bits int level = 1, max; while (((rnd >>>= 1) & 1) != 0) // 判斷從右到左有多少個連續的1 ++level; Index<K,V> idx = null; // 保存頭結點 HeadIndex<K,V> h = head; if (level <= (max = h.level)) { // 小於跳表的層級 for (int i = 1; i <= level; ++i) // 為結點生成對應的Index結點 // 從下至上依次賦值,並且賦值了Index結點的down域 idx = new Index<K,V>(z, idx, null); } else { // try to grow by one level level = max + 1; // hold in array and later pick the one to use // 生成Index結點的數組,其中,idxs[0]不作使用 @SuppressWarnings("unchecked")Index<K,V>[] idxs = (Index<K,V>[])new Index<?,?>[level+1]; for (int i = 1; i <= level; ++i) // 從下到上生成Index結點,並賦值down域 idxs[i] = idx = new Index<K,V>(z, idx, null); for (;;) { // 無限循環 // 保存頭結點 h = head; // 保存跳表之前的層級 int oldLevel = h.level; if (level <= oldLevel) // lost race to add level break; // 保存頭結點 HeadIndex<K,V> newh = h; // 保存頭結點對應的Node結點 Node<K,V> oldbase = h.node; for (int j = oldLevel+1; j <= level; ++j) // 為每一層生成一個頭結點 newh = new HeadIndex<K,V>(oldbase, newh, idxs[j], j); if (casHead(h, newh)) { // 比較並替換頭結點 // h賦值為最高層的頭結點 h = newh; // idx賦值為之前層級的頭結點,並將level賦值為之前的層級 idx = idxs[level = oldLevel]; break; } } } // find insertion points and splice in // 插入Index結點 splice: for (int insertionLevel = level;;) { // 保存新跳表的層級 int j = h.level; for (Index<K,V> q = h, r = q.right, t = idx;;) { if (q == null || t == null) // 頭結點或者idx結點為空 // 跳出外層循環 break splice; if (r != null) { // right結點不為空 // 保存r對應的Node結點 Node<K,V> n = r.node; // compare before deletion check avoids needing recheck // 比較key與結點的key值 int c = cpr(cmp, key, n.key); if (n.value == null) { // 結點的值為空,表示需要刪除 if (!q.unlink(r)) // 刪除q的Index結點 break; // r為q的right結點 r = q.right; continue; } if (c > 0) { // key大於結點的key // 向右尋找 q = r; r = r.right; continue; } } if (j == insertionLevel) { if (!q.link(r, t)) // r結點插入q與t之間 break; // restart if (t.node.value == null) { // t結點的值為空,需要刪除 // 利用findNode函數的副作用 findNode(key); break splice; } if (--insertionLevel == 0) // 到達最底層,跳出循環 break splice; } if (--j >= insertionLevel && j < level) t = t.down; q = q.down; r = q.right; } } } return null; }
說明:doPut提供對put函數的支持,doPut的大體流程如下:
① 根據給定的key從跳表的左上方往右或者往下查找到Node鏈表的前驅Node結點,這個查找過程會刪除一些已經標記為刪除的結點。
② 找到前驅結點后,開始往后插入查找插入的位置(因為找到前驅結點后,可能有另外一個線程在此前驅結點后插入了一個結點,所以步驟①得到的前驅現在可能不是要插入的結點的前驅,所以需要往后查找)。
③ 隨機生成一個種子,判斷是否需要增加層級,並且在各層級中插入對應的Index結點。
其中,會調用到findPredecessor函數,findPredecessor函數源碼如下

private Node<K,V> findPredecessor(Object key, Comparator<? super K> cmp) { if (key == null) // 鍵為空,拋出空異常 throw new NullPointerException(); // don't postpone errors for (;;) { // 無限循環 for (Index<K,V> q = head, r = q.right, d;;) { // if (r != null) { // 右Index結點不為空 // n為當前Node結點 Node<K,V> n = r.node; // 為當前key K k = n.key; if (n.value == null) { // 當前Node結點的value為空,表示需要刪除 if (!q.unlink(r)) // unlink r Index結點 break; // restart // r為rightIndex結點 r = q.right; // reread r continue; } if (cpr(cmp, key, k) > 0) { // 比較key與當前Node結點的k,若大於0 // 向右移動 q = r; r = r.right; continue; } } if ((d = q.down) == null) // q的down域為空,直接返回q對應的Node結點 return q.node; // 向下移動 q = d; // d的right結點 r = d.right; } } }
說明:findPredecessor函數的主要流程如下。
從頭結點(head)開始,先比較key與當前結點的key的大小,若key大於當前Index結點的key並且當前Index結點的right不為空,則向右移動,繼續查找;若當前Index結點的right為空,則向下移動,繼續查找;若key小於等於當前Index結點的key,則向下移動,繼續查找。直至找到前驅結點。
2. doRemove函數

// 移除一個結點 final V doRemove(Object key, Object value) { if (key == null) throw new NullPointerException(); // 保存比較器 Comparator<? super K> cmp = comparator; outer: for (;;) { // 無限循環 for (Node<K,V> b = findPredecessor(key, cmp), n = b.next;;) { // 根據key找到前驅結點,n為當前Index結點 Object v; int c; if (n == null) // n不為空 break outer; // f為當前結點的next結點 Node<K,V> f = n.next; if (n != b.next) // 不一致,重試 // inconsistent read break; if ((v = n.value) == null) { // 當前結點的value為空,需要刪除 // n is deleted // 刪除n結點 n.helpDelete(b, f); break; } if (b.value == null || v == n) // b is deleted break; if ((c = cpr(cmp, key, n.key)) < 0) // key小於當前結點的key // 跳出外層循環 break outer; if (c > 0) { // key大於當前結點的key // 向后移動 b = n; n = f; continue; } if (value != null && !value.equals(v)) break outer; if (!n.casValue(v, null)) // 當前結點的value設置為null break; if (!n.appendMarker(f) || !b.casNext(n, f)) // 在n結點后添加一個marker結點,並且將b的next域更新為f findNode(key); // retry via findNode else { // 添加節點並且更新均成功 // 利用findNode函數的副作用,刪除n結點對應的Index結點 findPredecessor(key, cmp); // clean index if (head.right == null) // 頭結點的right為null // 需要減少層級 tryReduceLevel(); } @SuppressWarnings("unchecked") V vv = (V)v; return vv; } } return null; }
說明:doRemove函數的處理流程如下。
① 根據key值找到前驅結點,查找的過程會刪除一個標記為刪除的結點。
② 從前驅結點往后查找該結點。
③ 在該結點后面添加一個marker結點,若添加成功,則將該結點的前驅的后繼設置為該結點之前的后繼。
④ 頭結點的next域是否為空,若為空,則減少層級。
下面的示意圖給出了remove操作一種可能的情況(僅僅涉及Node結點的鏈表層的操作)
說明:可以看到remove操作是分為兩步進行的,首先是在要刪除結點的后面添加一個marker結點,然后修改刪除結點的前驅結點的next域。注意,這里僅僅只給出了Node結點的鏈表層的操作,並沒有涉及到Index結點,關於Index結點的情況,之后會給出一個示例。其中會調用到tryReduceLevel函數,tryReduceLevel源碼如下

// 減少跳表層級 private void tryReduceLevel() { // 保存頭結點 HeadIndex<K,V> h = head; HeadIndex<K,V> d; HeadIndex<K,V> e; if (h.level > 3 && (d = (HeadIndex<K,V>)h.down) != null && (e = (HeadIndex<K,V>)d.down) != null && e.right == null && d.right == null && h.right == null && casHead(h, d) && // try to set h.right != null) // recheck casHead(d, h); // try to backout }
說明:如果最高的前三個HeadIndex不為空,並且其right域都為null,那么就將level減少1層,並將head設置為之前head的下一層,設置完成后,還有檢測之前的head的right域是否為null,如果為null,則減少層級成功,否則再次將head設置為h。
3. doGet函數

private V doGet(Object key) { if (key == null) throw new NullPointerException(); Comparator<? super K> cmp = comparator; outer: for (;;) { for (Node<K,V> b = findPredecessor(key, cmp), n = b.next;;) { // 根據key找到前驅結點,n為當前結點 Object v; int c; if (n == null) // 當前Index結點為null,跳出外層循環 break outer; // f為當前結點的next結點 Node<K,V> f = n.next; if (n != b.next) // 不一致,重試 // inconsistent read break; if ((v = n.value) == null) { // n is deleted n.helpDelete(b, f); break; } if (b.value == null || v == n) // b is deleted break; if ((c = cpr(cmp, key, n.key)) == 0) { // 找到key值相等的結點 @SuppressWarnings("unchecked") V vv = (V)v; // 返回value return vv; } if (c < 0) // 小於當前結點 // 則表示沒有找到,跳出外層循環 break outer; // 繼續向后移動 b = n; n = f; } } return null; }
說明:doGet函數流程比較簡單,首先根據key找到前驅結點,然后從前驅結點開始往后查找,找到與key相等的結點,則返回該結點,否則,返回null。在這個過程中會刪除一些已經標記為刪除狀態的結點。
4. size函數

public int size() { long count = 0; for (Node<K,V> n = findFirst(); n != null; n = n.next) { // 找到第一個結點 if (n.getValidValue() != null) // n結點沒有被標記刪除 // 計數器加1 ++count; } return (count >= Integer.MAX_VALUE) ? Integer.MAX_VALUE : (int) count; }
說明:size函數的流程如下,首先利用findFirst函數找到第一個value不為null的結點。然后開始往后遍歷,調用Node結點的getValidValue函數判斷結點的value是否有效,有效則計數器加1。其中findFirst源碼如下

final Node<K,V> findFirst() { for (Node<K,V> b, n;;) { if ((n = (b = head.node).next) == null) // 頭結點的下一個結點為當前結點,為null // 返回null return null; if (n.value != null) // 當前結點不為null // 則返回該結點 return n; // 表示當前結點的value為null,則進行刪除 n.helpDelete(b, n.next); } }
說明:findFirst函數的功能是找到第一個value不為null的結點。getValidValue源碼如下

V getValidValue() { Object v = value; if (v == this || v == BASE_HEADER) // value為自身或者為BASE_HEADER return null; @SuppressWarnings("unchecked") V vv = (V)v; return vv; }
說明:若結點的value為自身或者是BASE_HEADER,則返回null,否則返回結點的value。
四、示例
下面通過一個簡單的示例,來深入了解ConcurrentSkipListMap的內部結構。

package com.hust.grid.leesf.collections; import java.util.concurrent.ConcurrentSkipListMap; public class ConcurrentSkipListMapDemo { public static void main(String[] args) { ConcurrentSkipListMap<String, Integer> cslm = new ConcurrentSkipListMap<String, Integer>(); cslm.put("leesf", 24); cslm.put("dyd", 24); for (String key :cslm.keySet()) { System.out.print("[" + key + "," + cslm.get(key) + "] "); } System.out.println(); cslm.remove("leesf"); for (String key :cslm.keySet()) { System.out.print("[" + key + "," + cslm.get(key) + "] "); } } }
運行結果:
[dyd,24] [leesf,24]
[dyd,24]
說明:上面的一個示例非常簡單,下面借這個示例,來分析ConcurrentSkipListMap的內部結構。
① 當新生一個ConcurrentSkipListMap時,有如下結構。
② 當put("leesf", 24)后,可能有如下結構
說明:在插入一個Node結點的同時,也插入一個Index結點,並且head結點的right域指向該Index結點,該Index的Node域指向插入的Node結點。
③ 當put("dyd", 24)后,可能有如下結構
說明:插入的("dyd", 24),新生成的結點在leesf結點之前,並且也生成了一個Index結點指向它,此時跳表的層級還是為1。
④ 同樣,當put("dyd", 24)后,也可能有如下結構
說明:在插入("dyd", 24)后,層級加1,此時會生成兩個Index結點,並且兩個Index結點均指向新生成的Node結點。
⑤ 在remove("dyd")之后,存在如下結構
說明:在key為dyd的結點后面添加了一個marker結點(key為null,value為自身),並且Node結點對應的Index也將從Index鏈表中斷開,最后會被GC。
五、總結
看源代碼需要耐心,多思考,才能領略源碼的魅力所在,也謝謝各位園友的觀看~
參考鏈接:http://brokendreams.iteye.com/blog/2253955