SkipList和java中ConcurrentSkipListMap的實現java
一開始據說SkipList我是一臉懵逼的,啥?還有SkipList?這個是什麼玩意。node
後面通過個人不斷搜索和學習,終於明白了SkipList原來是一種數據結構,而java中的ConcurrentSkipListMap和ConcurrentSkipListSet就是這種結構的實現。數據結構
接下來就讓咱們一步一步的揭開SkipList和ConcurrentSkipListMap的面紗吧。併發
先看下維基百科中SkipList的定義:函數
SkipList是一種層級結構。最底層的是排序過的最原始的linked list。學習
往上是一層一層的層級結構,每一個底層節點按照必定的機率出如今上一層list中。這個機率叫作p,一般p取1/2或者1/4。this
先設定一個函數f,能夠隨機產生0和1這兩個數,而且這兩個數出現的概率是同樣的,那麼這時候的p就是1/2。spa
對每一個節點,咱們這樣操做:線程
咱們運行一次f,當f=1時,咱們將該節點插入到上層layer的list中去。當f=0時,不插入。指針
舉個例子,上圖中的list中有10個排序過的節點,第一個節點默認每層都有。對於第二個節點,運行f=0,不插入。對於第三個節點,運行f=1,將第三個節點插入layer 1,以此類推,最後獲得的layer 1 list中的節點有:1,3,4,6,9。
而後咱們再繼續往上構建layer。 最終獲得上圖的SkipList。
經過使用SkipList,咱們構建了多個List,包含不一樣的排序過的節點,從而提高List的查找效率。
咱們經過下圖能有一個更清晰的認識:
每次的查找都是從最頂層開始,由於最頂層的節點數最少,若是要查找的節點在list中的兩個節點中間,則向下移一層繼續查找,最終找到最底層要插入的位置,插入節點,而後再次調用機率函數f,決定是否向上複製節點。
其本質上至關於二分法查找,其查找的時間複雜度是O(logn)。
ConcurrentSkipListMap是一個併發的SkipList,那麼它具備兩個特色,SkipList和concurrent。咱們分別來說解。
上面講解了SkipList的數據結構,接下來看下ConcurrentSkipListMap是怎麼實現這個skipList的:
ConcurrentSkipListMap中有三種結構,base nodes,Head nodes和index nodes。
base nodes組成了有序的鏈表結構,是ConcurrentSkipListMap的最底層實現。
static final class Node<K,V> {
final K key;
volatile Object value;
volatile Node<K,V> next;
/** * Creates a new regular node. */
Node(K key, Object value, Node<K,V> next) {
this.key = key;
this.value = value;
this.next = next;
}
}
複製代碼
上面能夠看到每一個Node都是一個k,v的entry,而且其有一個next指向下一個節點。
index nodes是構建SkipList上層結構的基本節點:
static class Index<K,V> {
final Node<K,V> node;
final Index<K,V> down;
volatile Index<K,V> right;
/** * 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節點,除此以外,Index還有兩個指針,一個指向同一個layer的下一個節點,一個指向下一層layer的節點。
這樣的結構能夠方便遍歷的實現。
最後看一下HeadIndex,HeadIndex表明的是Head節點:
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) {
super(node, down, right);
this.level = level;
}
}
複製代碼
HeadIndex和Index很相似,只不過多了一個level字段,表示所在的層級。
在ConcurrentSkipListMap初始化的時候,會初始化HeadIndex:
head = new HeadIndex<K,V>(new Node<K,V>(null, BASE_HEADER, null),null, null, 1);
複製代碼
咱們能夠看到HeadIndex中的Node是key=null,value=BASE_HEADER的虛擬節點。初始的level=1。
接下來,咱們再看一下併發是怎麼實現的:
基本上併發類都是經過UNSAFE.compareAndSwapObject來實現的,ConcurrentSkipListMap也不例外。
假如咱們有三個節點,b-n-f。如今須要刪除節點n。
第一步,使用CAS將n的valu的值從non-null設置爲null。這個時候,任何外部的操做都會認爲這個節點是不存在的。可是那些內部的插入或者刪除操做仍是會繼續修改n的next指針。
第二步,使用CAS將n的next指針指向一個新的marker節點,從這個時候開始,n的next指針將不會指向任何其餘的節點。
咱們看下marker節點的定義:
Node(Node<K,V> next) {
this.key = null;
this.value = this;
this.next = next;
}
複製代碼
咱們能夠看到marker節點其實是一個key爲null,value是本身的節點。
第三步,使用CAS將b的next指針指向f。從這一步起,n節點不會再被其餘的程序訪問,這意味着n能夠被垃圾回收了。
咱們思考一下爲何要插入一個marker節點,這是由於咱們在刪除的時候,須要告訴全部的線程,節點n準備被刪除了,由於n原本就指向f節點,這個時候須要一箇中間節點來表示這個準備刪除的狀態。
本文從SkipList數據結構開始,講解了ConcurrentSkipListMap的實現。但願你們可以喜歡。
歡迎關注個人公衆號:程序那些事,更多精彩等着您! 更多內容請訪問:flydean的博客