ConcurrentSkipListMap原碼解析
SkipList介紹
1. SkipList(跳錶),在理論上能夠在O(log(n))時間內完成查詢、插入、刪除操作。SkipList是一種紅黑樹的替代方案,由於SkipList與紅黑樹相比無論從理論和實現都簡單許多,所以得到了很好的推廣。SkipList是基於一種統計學原理實現的,有可能出現最壞情況,即查詢和更新操作都是O(n)時間複雜度,但從統計學角度分析這種概率極小。使用SkipList型別的資料結構更容易控制多執行緒對集合訪問的處理,因為連結串列的區域性處理性比較好,當多個執行緒對SkipList進行更新操作(指插入和刪除)時,SkipList具有較好的區域性性,每個單獨的操作,對整體資料結構影響較小。而如果使用紅黑樹,很可能一個更新操作,將會波及整個樹的結構,其區域性性較差。因此使用SkipList更適合實現多個執行緒的併發處理。
2. ConcurrentSkipListMap採用Lock-Free Skip List實現。非併發版TreeMap是紅黑樹實現的,而為什麼併發版本不採用樹形結構呢,作者Doug Lea也說了,暫時沒有很好的無鎖操作樹形結構的演算法:The reason is that there are no known efficient lock-free insertion and deletion algorithms for search trees. 據說已經有lock-free trees的論文了: Lock-Free
Red-Black Trees Using CAS,相關問題可以參見
3. 呼叫ConcurrentSkipListMap的size時,由於多個執行緒可以同時對對映表進行操作,所以對映表需要遍歷整個連結串列才能返回元素個數,這個操作是個O(log(n))的操作。
原始碼解析(基於jdk1.8.0_40)
無鎖鏈表
ConcurrentSkipListMap能夠採用無鎖實現,是因為採用了無鎖操作(增加/刪除)連結串列的演算法--在刪除連結串列結點時,使用標記刪除的思想,先將結點value設定為null(步驟1),然後插入一個後繼marker結點(步驟2,如果失敗則說明有併發的insert,重試append marker就好了)。以後在查詢發現marker結點時才進行真正的刪除(步驟3)。這樣設計讓insert/delete操作需要競爭修改同一個結點的next指標,避免了併發操作出現insert被吃掉的情況(例如併發insert/delete操作單鏈表時,執行緒A
insert了一個結點x到n結點後面,而此時執行緒B立即delete掉了n結點,如果不加鎖或者append marker標記的話,新結點x就被一起誤刪了)。下面的演算法描述來源於該類的doc註釋:
* Here's the sequence of events for a deletion of node n with
* predecessor b and successor f, initially:
*
* +------+ +------+ +------+
* ... | b |------>| n |----->| f | ...
* +------+ +------+ +------+
*
* 1. CAS n's value field from non-null to null.
* From this point on, no public operations encountering
* the node consider this mapping to exist. However, other
* ongoing insertions and deletions might still modify
* n's next pointer.
*
* 2. CAS n's next pointer to point to a new marker node.
* From this point on, no other nodes can be appended to n.
* which avoids deletion errors in CAS-based linked lists.
*
* +------+ +------+ +------+ +------+
* ... | b |------>| n |----->|marker|------>| f | ...
* +------+ +------+ +------+ +------+
*
* 3. CAS b's next pointer over both n and its marker.
* From this point on, no new traversals will encounter n,
* and it can eventually be GCed.
* +------+ +------+
* ... | b |----------------------------------->| f | ...
* +------+ +------+
*
資料結構
/*
* This class implements a tree-like two-dimensionally linked skip
* list in which the index levels are represented in separate
* nodes from the base nodes holding data. There are two reasons
* for taking this approach instead of the usual array-based
* structure: 1) Array based implementations seem to encounter
* more complexity and overhead 2) We can use cheaper algorithms
* for the heavily-traversed index lists than can be used for the
* base lists. Here's a picture of some of the basics for a
* possible list with 2 levels of index:
*
* Head nodes Index nodes
* +-+ right +-+ +-+
* |2|---------------->| |--------------------->| |->null
* +-+ +-+ +-+
* | down | |
* v v v
* +-+ +-+ +-+ +-+ +-+ +-+
* |1|----------->| |->| |------>| |----------->| |------>| |->null
* +-+ +-+ +-+ +-+ +-+ +-+
* v | | | | |
* Nodes next v v v v v
* +-+ +-+ +-+ +-+ +-+ +-+ +-+ +-+ +-+ +-+ +-+ +-+
* | |->|A|->|B|->|C|->|D|->|E|->|F|->|G|->|H|->|I|->|J|->|K|->null
* +-+ +-+ +-+ +-+ +-+ +-+ +-+ +-+ +-+ +-+ +-+ +-+
*
這是一個level等於2的skip list,注意初始化時level為1。Level大於等於1的結點均為Index結點,特別的,最左邊的Index結點又是HeadIndex結點。而Level 0 也叫base-level,是Nodes單鏈表,是通過Level 1 的Index.node訪問到的。Node:
/**
* Nodes hold keys and values, and are singly linked in sorted
* order, possibly with some intervening marker nodes. The list is
* headed by a dummy node accessible as head.node. The value field
* is declared only as Object because it takes special non-V
* values for marker and header nodes.
*/
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;
}
/**
* 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;
}
/**
* compareAndSet value field
*/
boolean casValue(Object cmp, Object val) {
return UNSAFE.compareAndSwapObject(this, valueOffset, cmp, val);
}
/**
* compareAndSet next field
*/
boolean casNext(Node<K,V> cmp, Node<K,V> val) {
return UNSAFE.compareAndSwapObject(this, nextOffset, cmp, val);
}
/**
* Returns true if this node is a marker. This method isn't
* actually called in any current code checking for markers
* because callers will have already read value field and need
* to use that read (not another done here) and so directly
* test if value points to node.
*
* @return true if this node is a marker node
*/
boolean isMarker() {
return value == this;
}
/**
* Returns true if this node is the header of base-level list.
* @return true if this node is header node
*/
boolean isBaseHeader() {
return value == BASE_HEADER;
}
/**
* Tries to append a deletion marker to this node.
* @param f the assumed current successor of this node
* @return true if successful
*/
boolean appendMarker(Node<K,V> f) {
return casNext(f, new Node<K,V>(f));
}
/**
* Helps out a deletion by appending marker or unlinking from
* predecessor. This is called during traversals when value
* field seen to be null.
* @param b predecessor
* @param f successor
*/
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) {
if (f == null || f.value != f) // not already marked
casNext(f, new Node<K,V>(f));
else
b.casNext(this, f.next);
}
}
......
}
Index:
/**
* Index nodes represent the levels of the skip list. Note that
* even though both Nodes and Indexes have forward-pointing
* fields, they have different types and are handled in different
* ways, that can't nicely be captured by placing field in a
* shared abstract class.
*/
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;
}
/**
* compareAndSet right field
*/
final boolean casRight(Index<K,V> cmp, Index<K,V> val) {
return UNSAFE.compareAndSwapObject(this, rightOffset, cmp, val);
}
/**
* Returns true if the node this indexes has been deleted.
* @return true if indexed node is known to be deleted
*/
final boolean indexesDeletedNode() {
return node.value == null;
}
/**
* Tries to CAS newSucc as successor. To minimize races with
* unlink that may lose this index node, if the node being
* indexed is known to be deleted, it doesn't try to link in.
* @param succ the expected current successor
* @param newSucc the new successor
* @return true if successful
*/
final boolean link(Index<K,V> succ, Index<K,V> newSucc) {
Node<K,V> n = node;
newSucc.right = succ;
return n.value != null && casRight(succ, newSucc);
}
/**
* Tries to CAS right field to skip over apparent successor
* succ. Fails (forcing a retraversal by caller) if this node
* is known to be deleted.
* @param succ the expected current successor
* @return true if successful
*/
final boolean unlink(Index<K,V> succ) {
return node.value != null && casRight(succ, succ.right);
}
......
}
HeadIndex:
/**
* Nodes heading each level keep track of their level.
*/
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;
}
}
建構函式和初始化方法:
public ConcurrentSkipListMap() {
this.comparator = null;
initialize();
}
/**
* Constructs a new, empty map, sorted according to the specified
* comparator.
*
* @param comparator the comparator that will be used to order this map.
* If {@code null}, the {@linkplain Comparable natural
* ordering} of the keys will be used.
*/
public ConcurrentSkipListMap(Comparator<? super K> comparator) {
this.comparator = comparator;
initialize();
}
public ConcurrentSkipListMap(Map<? extends K, ? extends V> m) {
this.comparator = null;
initialize();
putAll(m);
}
public ConcurrentSkipListMap(SortedMap<K, ? extends V> m) {
this.comparator = m.comparator();
initialize();
buildFromSorted(m);
}
initialize方法,注意初始的HeadIndex level為1:
/**
* Initializes or resets state. Needed by constructors, clone,
* clear, readObject. and ConcurrentSkipListSet.clone.
* (Note that comparator must be separately initialized.)
*/
private void initialize() {
keySet = null;
entrySet = null;
values = null;
descendingMap = null;
head = new HeadIndex<K,V>(new Node<K,V>(null, BASE_HEADER, null),
null, null, 1);
}
doGet方法:
程式碼中的Node b, n, f 分別對應文章開頭圖示的單鏈表。過程比較清晰,先通過findPredecessor方法搜尋到base level層的Node結點,然後再繼續順序向後搜尋單鏈表,如果發現數據不一致的時候則回到outer迴圈重新查詢。 /**
* Gets value for key. Almost the same as findNode, but returns
* the found value (to avoid retries during re-reads)
*
* @param key the key
* @return the value, or null if absent
*/
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;;) {
Object v; int c;
if (n == null)
break outer;
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) {
@SuppressWarnings("unchecked") V vv = (V)v;
return vv;
}
if (c < 0)
break outer;
b = n;
n = f;
}
}
return null;
}
findPredecessor方法
通過索引查詢到Level 1層的Index結點q,然後返回q.node(即base level層的Node)。另外該方法還提供一個額外功能:發現Node結點被刪除後,刪除其Index索引結點。
/**
* Returns a base-level node with key strictly less than given key,
* or the base-level header if there is no such node. Also
* unlinks indexes to deleted nodes found along the way. Callers
* rely on this side-effect of clearing indices to deleted nodes.
* @param key the key
* @return a predecessor of key
*/
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) {
Node<K,V> n = r.node;
K k = n.key;
if (n.value == null) {//發現結點已被標記刪除,則呼叫前繼結點q.unlink()方法刪除Index索引結點
if (!q.unlink(r))
break; // restart
r = q.right; // reread r
continue;
}
if (cpr(cmp, key, k) > 0) {
q = r;
r = r.right;
continue;
}
}
if ((d = q.down) == null)
return q.node;
q = d;
r = d.right;
}
}
}
doPut方法:
第一步,outer迴圈,搜尋Skip List,找到key相等的結點則做更新操作,否則建立新結點並插入到base level層。
第二步,判斷是否要幫助新增結點建立索引,如果要,使用隨機數計算得到level值,作為要建立索引的層數。如果level>當前head.level,則要提升總Level層數(每次Level層數只增加1),並cas更新head屬性。然後構建Level 1 到level層的Index結點鏈(通過連結Index.down指標,後續再補建Index.right指標)。
第三步,splice迴圈,從level層開始逐層補建Index.right指標。
/**
* Main insertion method. Adds element if not present, or
* replaces value if present and onlyIfAbsent is false.
* @param key the key
* @param value the value that must be associated with key
* @param onlyIfAbsent if should not insert if already present
* @return the old value, or null if newly inserted
*/
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;;) {
if (n != null) {
Object v; int c;
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) {
b = 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))
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)
++level;
Index<K,V> idx = null;
HeadIndex<K,V> h = head;
if (level <= (max = h.level)) {
for (int i = 1; i <= level; ++i)
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
@SuppressWarnings("unchecked")Index<K,V>[] idxs =
(Index<K,V>[])new Index<?,?>[level+1];
for (int i = 1; i <= level; ++i)
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<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 = newh;
idx = idxs[level = oldLevel];
break;
}
}
}
// find insertion points and splice in
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)
break splice;
if (r != null) {
Node<K,V> n = r.node;
// compare before deletion check avoids needing recheck
int c = cpr(cmp, key, n.key);
if (n.value == null) {
if (!q.unlink(r))
break;
r = q.right;
continue;
}
if (c > 0) {
q = r;
r = r.right;
continue;
}
}
if (j == insertionLevel) {
if (!q.link(r, t))
break; // restart
if (t.node.value == null) {
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;
}
doRemove方法:
該方法流程類似doGet(),查詢到base level層的Node結點後,做標記刪除n.casValue(v, null),並append Marker結點。後續利用findPredecessor方法清除無效的Index索引結點。
/**
* Main deletion method. Locates node, nulls value, appends a
* deletion marker, unlinks predecessor, removes associated index
* nodes, and possibly reduces head index level.
*
* Index nodes are cleared out simply by calling findPredecessor.
* which unlinks indexes to deleted nodes found along path to key,
* which will include the indexes to this node. This is done
* unconditionally. We can't check beforehand whether there are
* index nodes because it might be the case that some or all
* indexes hadn't been inserted yet for this node during initial
* search for it, and we'd like to ensure lack of garbage
* retention, so must call to be sure.
*
* @param key the key
* @param value if non-null, the value that must be
* associated with key
* @return the node, or null if not found
*/
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;;) {
Object v; int c;
if (n == null)
break outer;
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)
break outer;
if (c > 0) {
b = n;
n = f;
continue;
}
if (value != null && !value.equals(v))
break outer;
if (!n.casValue(v, null))//標記刪除
break;
if (!n.appendMarker(f) || !b.casNext(n, f))//插入marker結點
findNode(key); // retry via findNode
else {
findPredecessor(key, cmp); // clean index
if (head.right == null)
tryReduceLevel();
}
@SuppressWarnings("unchecked") V vv = (V)v;
return vv;
}
}
return null;
}
相關內容連結
相關推薦
ConcurrentSkipListMap原碼解析
SkipList介紹 1. SkipList(跳錶),在理論上能夠在O(log(n))時間內完成查詢、插入、刪除操作。SkipList是一種紅黑樹的替代方案,由於SkipList與紅黑樹相比無論從理論和實現都簡單許多,所以得到了很好的推廣。SkipList是基於一種統計學
ConcurrentLinkedQueue原碼解析
描述 ConcurrentLinkedQueue是一個基於單鏈表的無界執行緒安全佇列,該佇列是FIFO的。ConcurrentLinkedQueue/ConcurrentLinkedDeue和LinkedBlockingQueue/LinkedBlockingDeue 相
Glide生命週期原碼解析
Glide生命週期管理的原碼其實就在 Glide.with(getContext()) 這個方法裡面 @NonNull public static RequestManager with(@NonNull Context context) { return
Okhttp3原碼解析(一)
首先看一下Okhttp3是怎麼進行請求的//建立OkHttpClient物件 OkHttpClient client = new OkHttpClient.Builder().readTimeout(5, TimeUnit.SECONDS) .writeTim
【JUC源碼解析】ConcurrentSkipListMap
sde 空指針 順序 pri 不一致 前驅 values dex warning 簡介 基於跳表,支持並發,有序的哈希表。 跳表 紅色路徑為尋找結點F。 拿空間換時間,時間復雜度,O(nlogn). 源碼分析 內部類 Node 屬性 1
C++ 原碼、補碼和反碼解析
首先對於有符號數,第一位的位置是表示的正負,為1時是負數,為0時是正數 其次,正數的原碼、補碼、反碼一致 負數的反碼是原碼的數值位(也就是除了第一位的符號位之外)取反,負數的補碼是反碼加1 在計算
原碼,反碼,補碼雜談
http 同余 而已 機器 wan 機器數 整理 把他 需要 本文從原碼講起。通過簡述原碼,反碼和補碼存在的作用,加深對補碼的認識。力爭讓你對補碼的概念不再局限於:負數的補碼等於反碼加一。 接觸過計算機或電子信息相關課程的同學,應該都或多或少看過補碼這哥仨。每次都是
laravel-index源碼解析
composer kernel 加載 時間 exceptio app 使用 def cati <?php /*設置腳本開始時間 define(‘LARAVEL_START‘, microtime(true)); 引入composer的自動加載,在composer
jQuery源碼解析(架構與依賴模塊)
源碼 cto and click dom元素 ack bsp 性能 selector 回溯處理 jQuery對象棧:jQuery內部維護著一個jQuery對象棧。每個遍歷方法都會找到一組新元素(一個jQuery對象),然後jQuery會把這組元素推入到棧中。 而每個jQue
opengl es入門---常見代碼解析
字符串數組 chm 視口 posit detail 編寫 組件 eat 包含著 轉自:http://blog.csdn.net/wangyuchun_799/article/details/7736928,尊重原創! 3.1創建渲染緩沖區 GLuint m
gunicorn syncworker 源碼解析
_for html bit int 文件的 ini exc mks list gunicorn支持不同的worker類型,同步或者異步,異步的話包括基於gevent、基於eventlet、基於Aiohttp(python版本需要大於3.3),也有多線程的版本。下面是gu
HashMap源代碼解析
new imu 感覺 sna 因子和 遍歷 會有 修改 color HashMap原理剖析 之前有看過別人的HashMap源代碼的分析,今天嘗試自己來分析一波,純屬個人愚見。聽一些老的程序員說過,當別人跟你說用某樣技術到項目中去,而你按照別人的想法實現了的時候,你只
ArrayList源碼解析(一)
unary 定義 cte 轉換 ora gif 成員類 con ins 目錄 1.位置 2.變量和常量 3.構造函數 4.trimToSize()方法 正文 源碼解析系列主要對Java的源碼進行詳細的說明,由於水平有限,難免出現錯誤或描述不準確的地方,還請大家指
Java二進制指令代碼解析
pos 無法 兩個 ade ceo default val center 時間 http://www.blogjava.net/DLevin/archive/2011/09/13/358497.html http://blog.csdn.net/sum_rain/art
02.15_Java語言基礎(原碼反碼補碼的講解).avi
cnblogs logs 基礎 java語言基礎 alt blog 補碼 http nbsp 02.15_Java語言基礎(原碼反碼補碼的講解).avi
Stack源碼解析
public syn 開頭 sync sys 彈出 for循環 last -s Stack介紹: 堆棧(Stack)是一個元素序列。盾戰中唯一能被刪除、訪問或修改的元素是最近插入的元素。這個元素就是位於堆棧頂部的那個元素。 舉例來說,自助餐廳的盤子架就是一個由盤子構
java基礎:原碼反碼補碼
gin 微軟雅黑 基礎 image p s 分享 ont style mil 計算機在操作的時候,都是采用數據對應二進制的補碼來計算的: 原碼 反碼 補碼 原碼:用原碼,反碼,補碼來分別表示+7,和-7. 首先得到7的二進制:111 java基
CopyOnWriteArrayList源碼解析
except set nts nbsp color cep 多線程 cnblogs 異常 CopyOnWriteArrayList是java1.5版本提供的一個線程安全的ArrayList變體。 在講解5.1.1ArrayList的時候,有說明ArrayLis
機器學習完整過程案例分布解析,python代碼解析
然而 表示 離散 好的 了解 成了 傳感器 att and 所謂學習問題,是指觀察由n個樣本組成的集合,並依據這些數據來預測未知數據的性質。 學習任務(一個二分類問題): 區分一個普通的互聯網檢索Query是否具有某個垂直領域的意圖。如果如今有一個O2O領域的垂直
ajax 底層源碼解析
源碼 不同 操作 cati 數據 t對象 增長 asc orm 對象: XMLHttpRequest屬性:readyState請求狀態,開始請求時值為0直到請求完成這個值增長到4 responseText目前為止接收到的響應體,readyState<3此屬性為空字符串