JDK container and concurrency-Map-ConcurrentSkipListMap
Overview
ConcurrentNavigableMap Based on the hop table.
1) The average time complexity of operations such as containsKey, get, put, and remove is log (n); size is not a fixed time operation. Due to the asynchronous nature, it is necessary to traverse all nodes to determine the size, the values may not be correct if they are modified during traversal. Batch operations: putAll, equals, toArray, containsValue, and clear are non-atomic.
2) Concurrent thread security for addition, deletion, modification, and query operations;
3) The iterator Is Weakly Consistent: When a map is created or a certain time point after it is created, ConcurrentModificationException is not thrown and may be executed concurrently with other operations. The ascending view and iterator are faster than the descending view;
Data Structure
Based on the linked list, there are three types of nodes: Node, Index, and HeadIndex. The bottom layer is the order layer (ascending) of the Node single-chain table, and the other layer is the Index Layer Based on the Node layer, that is, the Index layer, there may be multiple index layers.
Compared with a single-chain table, the Skip table search Node quickly means that the Index hierarchy is maintained through the HeadIndex, and the elements are searched from the top through the Index, narrowing the search scope step by step to the bottom Node Single-Chain surface layer, you only need a few elements to find the element nodes to be searched.
// Node, which has a key-value Pair // single-chain table and is ordered. The first Node is head. node to mark nodes. Some static final class nodes (namely, marker nodes) may be inserted in the middle.
{Final K key; volatile Object value; // value is of the Object type, which makes it easy to distinguish the volatile node of the marked Node from the marked node of the head. Node type.
Next;/*** Creates a new regular node. */Node (K key, Object value, Node
Next) {this. key = key; this. value = value; this. next = next;} // create a delete tag Node // Delete the value of the tag Node as this to distinguish other Node nodes; // The key is null, which is used in other scenarios, however, it cannot be used as a distinction because head. the node key is also nullNode (Node
Next) {this. key = null; this. value = this; this. next = next;} // CAS value Attribute boolean casValue (Object cmp, Object val) {return UNSAFE. compareAndSwapObject (this, valueOffset, cmp, val);} // CAS next attribute boolean casNext (Node
Cmp, Node
Val) {return UNSAFE. compareAndSwapObject (this, nextOffset, cmp, val);} // whether to delete the flag node boolean isMarker () {return value = this ;} // whether it is the header node boolean isBaseHeader () {return value = BASE_HEADER;} // Add the delete mark Node after the current node (implemented by CAS next) boolean appendMarker (Node
F) {return casNext (f, new Node
(F);} // promote the deletion of Node nodes. // generally, during traversal, if the value of the current Node is null, the void helpDelete (Node
B, Node
F) {// check whether the current link is B --> this --> f; // Delete the link in two steps, CAS is used for each step. // only push further at a time to reduce interference between threads. if (f = next & this = B. next) {// check whether it is B --> this --> f, where B is the precursor node, f is the successor node if (f = null | f. value! = F) // The node to be deleted is not marked with appendMarker (f); // The link to delete the tag node elseb. casNext (this, f. next); // Delete the current node and its marked nodes, and delete it.} // obtain the valid valueV getValidValue () {Object v = value of the node; if (v = this | v = BASE_HEADER) // if the flag node and header node are to be deleted, nullreturn null; return (V) v;} is returned ;} // encapsulate valid key-value pairs into immutable EntryAbstractMap. simpleImmutableEntry
CreateSnapshot () {V v = getValidValue (); if (v = null) return null; return new AbstractMap. SimpleImmutableEntry
(Key, v);} // UNSAFE mechanic sprivate static final sun. misc. unsafe UNSAFE; private static final long valueOffset; 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) ;}}// index node, used to narrow down the search range from the top to the bottom // logical double-chain table, instead of the front and back links, but the up and down links static class Index
{Final Node
Node; // The Index Node is based on the final Index of the node Node.
Down; // if the value of down is final, the concurrency volatile Index is simplified.
Right;/*** Creates index node with given values. */Index (Node
Node, Index
Down, Index
Right) {this. node = node; this. down = down; this. right = right;} // CAS right attribute final boolean casRight (Index
Cmp, Index
Val) {return UNSAFE. compareAndSwapObject (this, rightOffset, cmp, val);} // whether its Node deletes final boolean indexesDeletedNode () {return node. value = null;} // link to the Index Node // If the Node has been deleted during the link process, it is not linked, to reduce the CAS competition with the solution/** @ param succ the expected current successor * @ param newSucc the new successor * @ return true if successful */final boolean link (Index
Succ, Index
NewSucc) {Node
N = node; newSucc. right = succ; // link newSucc to return n. value! = Null & casRight (succ, newSucc); // CAS right} // unlink the index Node. If its Node has been deleted, failed to unlink/*** @ param succ the expected current successor * @ return true if successful */final boolean unlink (Index
Succ) {return! IndexesDeletedNode () & casRight (succ, succ. right); // CAS right} // Unsafe mechanic sprivate 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) ;}}// HeadIndex node, tracking the index level static final class HeadIndex
Extends Index
{Final int level; // index layer. Starting from 1, the Node Single-Chain surface is 0 HeadIndex (Node
Node, Index
Down, Index
Right, int level) {super (node, down, right); this. level = level ;}}
Structure of an instance in JDK:
The following is an explanation of the table on WIKI: <喎?http: www.bkjia.com kf ware vc " target="_blank" class="keylink"> VcD4KPHA + pgltzybzcm9 "http://www.2cto.com/uploadfile/Collfiles/20160421/201604210924001082.gif" alt = "\">
Constructor
No parameter construction, empty Mappublic ConcurrentSkipListMap () {this. comparator = null; // sort initialize () using the Comparable interface of the Key;} // construct public ConcurrentSkipListMap (comparator) with the Comparator Parameter
Comparator) {this. comparator = comparator; initialize () ;}// construct with Map parameters. Sort public ConcurrentSkipListMap (Map
M) {this. comparator = null; initialize (); putAll (m);} // construct with SortedMap parameter. Sort public ConcurrentSkipListMap (SortedMap
M) {this. comparator = m. comparator (); initialize (); buildFromSorted (m );}
Addition, deletion, modification, query, and initialization
Final void initialize () {keySet = null; entrySet = null; values = null; descendingMap = null; randomSeed = seedGenerator. nextInt () | 0x0100; // ensure nonzerohead = new HeadIndex
(New Node
(Null, BASE_HEADER, null), null, null, 1); // initialize the head node, empty Map}
Add and modify
Steps:
1) Find a precursor Node smaller than the key, and delete the index Node of the Node to be deleted during the search;
2) traverse the underlying Node single-chain table from the front-end Node. If a key-value Pair exists, CAS replaces the new value and returns the old value. If no key-value Pair exists, the insertion location is determined. During the traversal process, the Node to be deleted is promoted;
3) create a new Node with key and value and link it with CAS next;
4) A Random Index hierarchy is generated for the new Node. If the hierarchy is greater than 0, an index Node is added to the Node and null is returned.
Public V put (K key, V value) {if (value = null) throw new NullPointerException (); return doPut (key, value, false );} private V doPut (K kkey, V value, boolean onlyIfAbsent) {Comparable
Key = comparable (kkey); for (;) {Node
B = findPredecessor (key); // after finding the precursor Node, the next step is to precisely locate the insertion position on the Node single-link Surface
N = B. next; for (;) {// traverses the clear Node operation, which is the same as findNodeif (n! = Null) {Node
F = n. next; if (n! = B. next) break; Object v = n. value; if (v = null) {n. helpDelete (B, f); break;} if (v = n | B. value = null) break; int c = key. compareTo (n. key); if (c> 0) {// key is greater than n, continue to find B = n; n = f; continue;} if (c = 0) {// existing key-value pairs related to if (onlyIfAbsent | n. casValue (v, value) return (V) v; elsebreak; // If CAS value fails, start again, the cause of failure may be that n is changed to the node to be deleted or another Modified Thread has been modified} // else c <0; fall through: indicates that the new key-value pair needs to be inserted between B and n} Node
Z = new Node
(Kkey, value, n); if (! B. casNext (n, z) // Insert the new node z into the break between nodes B and n. // The failure is due to the same reason as n! = B. next, return int level = randomLevel (); // randomly generates an index level if (level> 0) insertIndex (z, level) for the newly added Node ); // Add the index node return null to z;} // find the precursor node smaller than the key. If no, return head. node // some operations depend on this method to delete the index Node private node
FindPredecessor (Comparable
Key) {if (key = null) throw new NullPointerException (); // don't postpone errorsfor (;) {Index
Q = head; Index
R = q. right; // start from the top of the index layer and go down to the right. // always locate the bottom index layer (that is, the first layer) to determine the search range, find for (;) {if (r! = Null) {// At the index layer, locate the Node to the right
N = r. node; K k = n. key; if (n. value = null) {// traverse the index node to delete node n if (! Q. unlink (r) // delete its index node (using the CAS right attribute) // cause of deletion failure: q is marked as a node to be deleted, a new index node is added after q, or its right node break is deleted. // re-start r = q. right; // if the deletion is successful, obtain the New right index node and continue to find the continue;} if (key. compareTo (k)> 0) {// if the key is large, it indicates that there may be greater than the key. Continue to find q = r; r = r. right; continue ;}} Index
D = q. down; // if the index layer does not exist at the layer, search for the next layer to further narrow the search scope if (d! = Null) {// At the next index layer, continue to find q = d; r = d. right;} elsereturn q. node; // determine the parent node. If not, it is head. node tag Node }}// randomly generate an index level for the newly added node/*** Returns a random level for inserting a new node. * Hardwired to k = 1, p = 0.5, max 31 (see above and * guarantees's "Skip List Cookbook", sec 3.4 ). ** This uses the simplest of the generators described in George * Marsaglia's "Xorshift RNGs" paper. this is not a high-quality * generator But is acceptable here. */private int randomLevel () {int x = randomSeed; x ^ = x <13; x ^ = x >>>> 17; randomSeed = x ^ = x <5; if (x & 0x80000001 )! = 0) // test highest and lowest bitsreturn 0; int level = 1; while (x >>>= 1) & 1 )! = 0) ++ level; return level ;} // Add an index Node for the node Node/*** @ param z the Node * @ param level the level of the index */private void insertIndex (node
Z, int level) {HeadIndex
H = head; int max = h. level; // The index level of the head is the largest if (level <= max) {// The index level of the index node to be added is within the head index level, create an Index node and add it to the Index
Idx = null; for (int I = 1; I <= level; ++ I) idx = new Index
(Z, idx, null); // index node, link addIndex (idx, h, level) from the bottom up ); // link the index node To} else {// Add an index layer. The new level of HeadIndex/** To reduce interference by other threads checking for * empty levels in try1_celevel is required, new levels are added * with initialized right pointers. which in turn requires * keeping levels in an array to access them while * creating new head index nodes from the opposite * direction. */level = max + 1; Index
[] Idxs = (Index
[]) New Index [level + 1]; Index
Idx = null; for (int I = 1; I <= level; ++ I) idxs [I] = idx = new Index
(Z, idx, null); HeadIndex
Oldh; int k; for (;) {oldh = head; int oldLevel = oldh. level; if (level <= oldLevel) {// other threads have added the index layer k = level; break; // level <= max processing} HeadIndex
Newh = oldh; Node
Oldbase = oldh. node; for (int j = oldLevel + 1; j <= level; ++ j) // other threads may have deleted the index layer, therefore, HeadIndexnewh = new HeadIndex is added from oldLevel to level.
(Oldbase, newh, idxs [j], j); // create a new level of HeadIndex and link the corresponding level of index node idxs to if (casHead (oldh, newh )) {// CAS head HeadIndex node k = oldLevel; break ;}} addIndex (idxs [k], oldh, k ); // The old oldLevel level of idxs and the following indexes need to be linked in}/*** from the indexLevel layer down to the 1st layer, link the index node * @ param idx the topmost index node being inserted * @ param h the value of head to use to insert. this must be * snapshotted by callers to provide correct insertion level * @ param indexLevel the level of the index */private void addIndex (Index
Idx, HeadIndex
H, int indexLevel) {// Track next level to insert in case of retriesint insertionLevel = indexLevel; Comparable
Key = comparable (idx. node. key); if (key = null) throw new NullPointerException (); // The process is similar to findPredecessor, but the index node for (;) {int j = h is added. level; Index
Q = h; Index
R = q. right; Index
T = idx; for (;) {if (r! = Null) {// At the index layer, traverse the Node to the right
N = r. node; // compare before deletion check avoids needing recheckint c = key. compareTo (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) {// you can link the index node idxif (t. indexesDeletedNode () {// The Node of the index Node is marked as the findNode (key) of the Node to be deleted; // promote the return of the index Node and Its Node to be deleted; // no need to add index nodes} if (! Q. link (r, t) // link the insertionLevel-level index node to the link // cause of deletion failure: Same as q in findPredecessor. unlink (r) break; // The Link fails. Restart if (-- insertionLevel = 0) {// prepare the next layer of index if (t. indexesDeletedNode () // check whether the index node t is marked as a node to be deleted before returning the result to findNode (key); return; // insertionLevel = 0 indicates that the link to the index node idx has been completed} if (-- j> = insertionLevel & j <indexLevel) // The insertionLevel + layer 1 t = t of the linked index node idx. down; // prepare the insertionLevel layer q = q of the index node idx. down ; // Prepare the Index r = q. right ;}}/// next layer of the index Node idx. If no index is found, null is returned. // Traverse the Node single-chain table and clear the nodes to be deleted. // such a clear traversal operation is included in doPut, doRemove, findNear, and so on. // The clear Code cannot be shared, because you need to obtain snapshots of the Node linked list order for addition, deletion, modification, and query, and store them to local variables. This method is used to delete the private Node of a Node in concurrency // some operations depend on this method.
FindNode (Comparable
Key) {for (;) {Node
B = findPredecessor (key); // The Node where the key is obtained.
N = B. next; for (;) {if (n = null) return null; Node
F = n. next; // It is not a continuous B --> n --> f snapshot. You cannot perform subsequent unlink reception to delete nodes. // Changes: after B, a new node is added, its next node is deleted, or the delete mark node is added to delete B, if (n! = B. next) break; // re-start Object v = n. value; if (v = null) {// n is the node to be deleted n. helpDelete (B, f); // promotes the deletion of node nbreak; // restarts} // The returned precursor Node B is the node to be deleted. // You cannot delete B directly here, because I do not know the precursor node of B, I can only start again. I call findPredecessor to return the earlier node if (v = n | B. value = null) // B is deletedbreak; // restart int c = key. compareTo (n. key); if (c = 0) return n; if (c <0) return null; B = n; n = f ;}}}
Delete
Steps:
1) Find the front node smaller than the key;
2) from the front-end Node, traverse the underlying Node single-chain table. If no key-value Pair exists, null is returned. Otherwise, the Node location is determined. Assume that the deleted Node is n, B is its precursor node, and f is its successor node:
3) Use CAS to set its value to null;
Purpose:
A) when other add, delete, modify, and query threads traverse the node, they all know that it is the node to be deleted;
B) other add, delete, modify, and query threads can modify n's next through CAS to promote n's deletion.
This step fails. You only need to try again.
4) add and delete the mark node marker;
Purpose:
A) The new node cannot be inserted after n;
B) CAS-based deletion can avoid deletion errors.
5) Delete the node and its marked nodes;
4th) 5) the step may fail because other operation threads know that n's value is null during the traversal process and will help to delete n, these help Operations ensure that no thread is blocked due to the deletion operation of the deletion thread.
In addition, you must ensure that the link B --> n --> marker --> f can be deleted.
6) Use findPredecessor to delete its index node;
7) if no Node index Node exists at the top index layer, try to lower the index level.
8) returns the old value.
Public V remove (Object key) {return doRemove (key, null);} // mainly deletes Node nodes and Its Index Node Method final V doRemove (Object okey, Object value) {Comparable
Key = comparable (okey); for (;) {Node
B = findPredecessor (key); Node
N = B. next; for (;) {if (n = null) return null; Node
F = n. next; if (n! = B. next) // inconsistent readbreak; Object v = n. value; if (v = null) {// n is deletedn. helpDelete (B, f); break;} if (v = n | B. value = null) // B is deletedbreak; int c = key. compareTo (n. key); if (c <0) return null; if (c> 0) {B = n; n = f; continue;} if (value! = Null &&! Value. equals (v) return null; if (! N. casValue (v, null) // set the value to nullbreak; if (! N. appendMarker (f) |! B. casNext (n, f) // add or delete a marked node to delete the marked node findNode (key ); // if it fails, use findNode to continue deleting else {findPredecessor (key); // use findPredecessor to delete its index node if (head. right = null) tryReduceLevel () ;}return (V) v ;}}// when the top three layers of the index layer have no Node index nodes, the top three layers of the index layer are removed. // If CAS is used to remove a Node index Node, try to restore it. Private void tryinclucelevel () {HeadIndex
H = head; HeadIndex
D; HeadIndex
E; if (h. level> 3 & (d = (HeadIndex
) H. down )! = Null & (e = (HeadIndex
) D. down )! = Null & e. right = null & d. right = null & h. right = null & casHead (h, d) & // try to seth. right! = Null) // recheckcasHead (d, h); // try to backout}
Query
Steps:
1) Find the front node smaller than the key;
2) traverse the underlying Node single-chain table from the front-end Node. If no key-value Pair exists, null is returned; otherwise, the Node is obtained;
3) judge whether the value is null. If the value is not null, return the value directly; otherwise, retry because it is marked as a node to be deleted.
public V get(Object key) {return doGet(key);}private V doGet(Object okey) {Comparable
key = comparable(okey);/* * Loop needed here and elsewhere in case value field goes * null just as it is about to be returned, in which case we * lost a race with a deletion, so must retry. */for (;;) {Node
n = findNode(key);if (n == null)return null;Object v = n.value;if (v != null)return (V)v;}}
Iterator
The basic iterator is Iter, which traverses the Node Single-Chain surface layer from the first Node:
abstract class Iter
implements Iterator
{/** the last node returned by next() */Node
lastReturned;/** the next node to return from next(); */Node
next;/** Cache of next value field to maintain weak consistency */V nextValue;/** Initializes ascending iterator for entire range. */Iter() {for (;;) {next = findFirst();if (next == null)break;Object x = next.value;if (x != null && x != next) {nextValue = (V) x;break;}}}public final boolean hasNext() {return next != null;}/** Advances next to higher entry. */final void advance() {if (next == null)throw new NoSuchElementException();lastReturned = next;for (;;) {next = next.next; // nextif (next == null)break;Object x = next.value;if (x != null && x != next) {nextValue = (V) x;break;}}}public void remove() {Node
l = lastReturned;if (l == null)throw new IllegalStateException();// It would not be worth all of the overhead to directly// unlink from here. Using remove is fast enough.ConcurrentSkipListMap.this.remove(l.key);lastReturned = null;}}
Features
How to Implement concurrent thread security for addition, deletion, modification, and query?
1. Adopt the lock-free concurrency mode;
2. The final, volatile, and CAS methods help with concurrency;
3. delete a Node: set the value of the Node to null, and insert a Delete label Node after it, that is: B --> n --> marker --> f (assume n is the Node to be deleted, marker deletes the marked Node, and B is the precursor Node of n, this method solves four problems:
1) insert with Node can be performed concurrently, because after n is marked as a Node for marker, it will certainly not insert a new Node after n;
2) The modification can be performed concurrently with the Node. Because the value of n is null, the CAS modification to n by the modification thread must fail;
3) it can be read concurrently with Node because the value of n is null. Even if the read thread matches n, the returned value is null, in Map, if null is returned, it indicates that the key-value Pair does not exist and n is being deleted. Therefore, it indicates that n does not exist, although not strictly;
4) when traversing a Node single-chain table, you can delete n and marker based on the link above.