SkipList和java中ConcurrentSkipListMap的实现
文章目錄
- 簡介
- SkipList
- ConcurrentSkipListMap
- SkipList的實現(xiàn)
- concurrent的實現(xiàn)
- 總結(jié)
SkipList和java中ConcurrentSkipListMap的實現(xiàn)
簡介
一開始聽說SkipList我是一臉懵逼的,啥?還有SkipList?這個是什么玩意。
后面經(jīng)過我的不斷搜索和學習,終于明白了SkipList原來是一種數(shù)據(jù)結(jié)構(gòu),而java中的ConcurrentSkipListMap和ConcurrentSkipListSet就是這種結(jié)構(gòu)的實現(xiàn)。
接下來就讓我們一步一步的揭開SkipList和ConcurrentSkipListMap的面紗吧。
SkipList
先看下維基百科中SkipList的定義:
SkipList是一種層級結(jié)構(gòu)。最底層的是排序過的最原始的linked list。
往上是一層一層的層級結(jié)構(gòu),每個底層節(jié)點按照一定的概率出現(xiàn)在上一層list中。這個概率叫做p,通常p取1/2或者1/4。
先設(shè)定一個函數(shù)f,可以隨機產(chǎn)生0和1這兩個數(shù),并且這兩個數(shù)出現(xiàn)的幾率是一樣的,那么這時候的p就是1/2。
對每個節(jié)點,我們這樣操作:
我們運行一次f,當f=1時,我們將該節(jié)點插入到上層layer的list中去。當f=0時,不插入。
舉個例子,上圖中的list中有10個排序過的節(jié)點,第一個節(jié)點默認每層都有。對于第二個節(jié)點,運行f=0,不插入。對于第三個節(jié)點,運行f=1,將第三個節(jié)點插入layer 1,以此類推,最后得到的layer 1 list中的節(jié)點有:1,3,4,6,9。
然后我們再繼續(xù)往上構(gòu)建layer。 最終得到上圖的SkipList。
通過使用SkipList,我們構(gòu)建了多個List,包含不同的排序過的節(jié)點,從而提升List的查找效率。
我們通過下圖能有一個更清晰的認識:
每次的查找都是從最頂層開始,因為最頂層的節(jié)點數(shù)最少,如果要查找的節(jié)點在list中的兩個節(jié)點中間,則向下移一層繼續(xù)查找,最終找到最底層要插入的位置,插入節(jié)點,然后再次調(diào)用概率函數(shù)f,決定是否向上復(fù)制節(jié)點。
其本質(zhì)上相當于二分法查找,其查找的時間復(fù)雜度是O(logn)。
ConcurrentSkipListMap
ConcurrentSkipListMap是一個并發(fā)的SkipList,那么它具有兩個特點,SkipList和concurrent。我們分別來講解。
SkipList的實現(xiàn)
上面講解了SkipList的數(shù)據(jù)結(jié)構(gòu),接下來看下ConcurrentSkipListMap是怎么實現(xiàn)這個skipList的:
ConcurrentSkipListMap中有三種結(jié)構(gòu),base nodes,Head nodes和index nodes。
base nodes組成了有序的鏈表結(jié)構(gòu),是ConcurrentSkipListMap的最底層實現(xiàn)。
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指向下一個節(jié)點。
index nodes是構(gòu)建SkipList上層結(jié)構(gòu)的基本節(jié)點:
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;}}從上面的構(gòu)造我們可以看到,Index節(jié)點包含了Node節(jié)點,除此之外,Index還有兩個指針,一個指向同一個layer的下一個節(jié)點,一個指向下一層layer的節(jié)點。
這樣的結(jié)構(gòu)可以方便遍歷的實現(xiàn)。
最后看一下HeadIndex,HeadIndex代表的是Head節(jié)點:
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的虛擬節(jié)點。初始的level=1。
concurrent的實現(xiàn)
接下來,我們再看一下并發(fā)是怎么實現(xiàn)的:
基本上并發(fā)類都是通過UNSAFE.compareAndSwapObject來實現(xiàn)的,ConcurrentSkipListMap也不例外。
假如我們有三個節(jié)點,b-n-f。現(xiàn)在需要刪除節(jié)點n。
第一步,使用CAS將n的valu的值從non-null設(shè)置為null。這個時候,任何外部的操作都會認為這個節(jié)點是不存在的。但是那些內(nèi)部的插入或者刪除操作還是會繼續(xù)修改n的next指針。
第二步,使用CAS將n的next指針指向一個新的marker節(jié)點,從這個時候開始,n的next指針將不會指向任何其他的節(jié)點。
我們看下marker節(jié)點的定義:
Node(Node<K,V> next) {this.key = null;this.value = this;this.next = next;}我們可以看到marker節(jié)點實際上是一個key為null,value是自己的節(jié)點。
第三步,使用CAS將b的next指針指向f。從這一步起,n節(jié)點不會再被其他的程序訪問,這意味著n可以被垃圾回收了。
我們思考一下為什么要插入一個marker節(jié)點,這是因為我們在刪除的時候,需要告訴所有的線程,節(jié)點n準備被刪除了,因為n本來就指向f節(jié)點,這個時候需要一個中間節(jié)點來表示這個準備刪除的狀態(tài)。
總結(jié)
本文從SkipList數(shù)據(jù)結(jié)構(gòu)開始,講解了ConcurrentSkipListMap的實現(xiàn)。希望大家能夠喜歡。
更多精彩內(nèi)容且看:
- 區(qū)塊鏈從入門到放棄系列教程-涵蓋密碼學,超級賬本,以太坊,Libra,比特幣等持續(xù)更新
- Spring Boot 2.X系列教程:七天從無到有掌握Spring Boot-持續(xù)更新
- Spring 5.X系列教程:滿足你對Spring5的一切想象-持續(xù)更新
- java程序員從小工到專家成神之路(2020版)-持續(xù)更新中,附詳細文章教程
歡迎關(guān)注我的公眾號:程序那些事,更多精彩等著您!
更多內(nèi)容請訪問:flydean的博客
總結(jié)
以上是生活随笔為你收集整理的SkipList和java中ConcurrentSkipListMap的实现的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 一文弄懂EnumMap和EnumSet
- 下一篇: 一文弄懂java中的Queue家族