JDK1.6 version
CONCURRENTHASHMAP Structure
In JDK1.6, Concurrenthashmap divides the data into a section of storage, giving each piece of data a lock, when a thread obtains a lock mutex accesses a segment of data, other segments of the data can also be accessed by other threads; Each segment has a reentrant lock, so Concurrenthashmap The number of segment locks is the length of the segment array. CONCURRENTHASHMAP structure: Each segment is a hashentry<k,v>[] table, each element in the table is essentially a hashentry one-way queue (one-way linked list implementation). Each segment is a hashentry<k,v>[] table, and each element in the table is essentially a hashentry one-way queue. Lock Separation Implementation
When a thread accesses node/key-value pairs of data, it must obtain the corresponding segment lock, and other threads can access the data in other segment (lock separation); Concurrenthashmap Declaration
> public class Concurrenthashmap<k,v> extends abstractmap<k,v> implements CONCURRENTMAP<K,V> Serializable Lock- free algorithm: CAS optimistic lock and pessimistic lock pessimistic lock such as synchronized lock, in order to ensure that other threads do not interfere with the current thread work, so hang up other threads that need to lock, Wait for the thread that holds the lock to be released; optimistic locks always assume that no conflict occurs, and if a conflict is detected, it fails to retry until it succeeds; CAs algorithm
CAs (Compare and Swap): CAS algorithm contains three parameters CAs (V, E, N) to determine if the expected value E and memory old values are the same (Compare), if equal with the new value N overwrite the old value V (Swap ), otherwise failed; When multiple threads try to update the same variable simultaneously using CAS, only one of the threads can update the value of the variable, and the other thread fails (the failed thread is not blocked, but is told "failed" and can continue to try); CAs can be compiled into machine instruction execution at the hardware level. Therefore, the performance is higher than the lock-based approach for thread safety; CONCURRENTHASHMAP structure diagram
Compare with JDK1.6
JDK 1.8 Cancels the class segments field, directly stores the key value pair with the table array, each bucket in the JDK1.6 the key value pair organization is the unidirectional list, the finding complexity is O (n), JDK1.8 when the list length exceeds treeify_threshold, Linked list to red black tree, query complexity can be reduced to O (log n), improve performance;
The segment class differs greatly from the previous version of the JDK in JDK1.8, JDK1.8, which has failed in the normal concurrenthashmap operation, primarily to be compatible with the previous JDK version and play a role in serialization and deserialization. Lock Separation
In JDK1.8, each time a thread locks a bucket (linked list or red black tree), other threads still have access to the other buckets, and concurrency control is specific to the bucket, that is, how many buckets can allow the number of concurrent numbers; Thread safety
Concurrenthashmap the underlying data structure is the same as the HashMap, still using table array + linked list + red-black tree structure; When a thread is Put/remove, the bucket (list or Red and black tree) plus synchronized exclusive lock; Concurrenthashmap using CAS algorithm to ensure thread safety ; concurrenthashmap basic data Structure
Transient volatile node< K,v> [] Table: Key-value-to-bucket array
Private transient volatile node< K,v> [] Nexttable:rehash the new key value to be used when expanding the array
Private transient volatile long basecount: Records the total number of current key-value pairs, updated by CAs, visible to all threads
private transient volatile int sizectl:sizectl represents the total threshold value of the key-value pair, updated by CAs, visible to all threads > when Sizectl < 0 o'clock indicates that multiple threads are waiting to be expanded; > when sizectl = 0 o'clock, default value; > When Sizectl > 0 o'clock, indicates the threshold for expansion;
private transient volatile int cellbusy: spin lock;
Private transient volatile countercell[] countercells:counter cell table with a total length of 2 power;
Static Class segment< K,V>: In JDK1.8, the segment class is only useful in serialization and deserialization;
View
private transient keysetview<k,v> keySet
private transient valuesview<k,v> values
Private transient entrysetview<k,v> EntrySet
describe key-value pairs: node<k, v>
Static Class Node<k,v> implements map.entry<k,v> {
final int hash;
Final K key;
The value and next of the key-value pairs are volatile type
volatile V val;
Volatile node<k,v> next;
...
}
analysis of important methods of Concurrenthashmap
constructor Function
concurrenthashmap (int initialcapacity, float loadfactor, int concurrencylevel)
Public Concurrenthashmap (int initialcapacity,
float loadfactor, int concurrencylevel) {
if (!) ( Loadfactor > 0.0f) | | Initialcapacity < 0 | | Concurrencylevel <= 0)
throw new IllegalArgumentException ();
if (Initialcapacity < Concurrencylevel) //use at least as many bins
initialcapacity = concurrencylevel; As estimated threads
long size = (long) (1.0 + (long) initialcapacity/loadfactor);
int cap = (size >= (long) maximum_capacity)?
Maximum_capacity:tablesizefor ((int) size);
This.sizectl = cap;
}
The constructor determines the minimum 2 recurses of a >= initialcapacity based on the input initialcapacity;
Concurrentlevel: The total number of Concurrenthashmap segment locks before JDK1.8, representing the maximum number of threads that update concurrenthashmap at the same time and do not generate lock contention; in JDK1.8, only the initial capacity is ensured in the constructor >=concurrentlevel, reserved for compatibility with older versions; initialization method: Inittable
For Concurrenthashmap, the constructor to invoke it is simply to set some parameters. The initialization of the entire table occurs when an element is inserted into the concurrenthashmap. If you call put, computeifabsent, compute, Merge and other methods, the call time is to check the table==null.
The initialization method mainly applies the key attribute Sizectl if this value is 〈0, indicating that another thread is initializing, discard the operation. It can also be seen that the initialization of **concurrenthashmap can only be done by a single thread. If the initialization permission is obtained, the CAs method is used to set Sizectl to 1 to prevent other threads from entering. * * After initializing the array, change the value of Sizectl to 0.75*n
/** * Initializes table, using the size recorded in Sizectl.
*/private final node<k,v>[] inittable () {node<k,v>[] tab; int SC; while (tab = table) = = NULL | | tab.length = = 0) {//sizectl indicates that another thread is initializing, suspending the thread.
For the initialization of table, only one thread is in progress. if (sc = Sizectl) < 0) Thread.yield (); Lost initialization race;
Just spin else if (U.compareandswapint (this, Sizectl, SC,-1)) {//Use the CAs method to set the value of Sizectl to 1 to indicate that this thread is initializing try {if (tab = table) = = NULL | | tab.length = = 0) {int n = (sc > 0)?
sc:default_capacity;
@SuppressWarnings ("Unchecked") node<k,v>[] nt = (node<k,v>[]) new node<?,? >[n];
Table = Tab = NT; sc = n-(n >>> 2);//equivalent to 0.75*n setting a threshold value for expansion}} finally {Si ZectL = SC;
} break;
}} return tab;
}
Add/Update key-value pairs: Putval
Putval Method Analysis
Final V Putval (K key, V value, Boolean onlyifabsent) {//does not allow key or value to be null value if (key = = NULL | | value = = NULL) throw new Nu Llpointerexception (); int hash = spread (Key.hashcode ()); int bincount = 0;
Continuous cas probing if other threads are modifying Tab,cas attempt to fail until successful for (node<k,v>[] tab = table;;)
{node<k,v> F; int n, I, FH; Empty table, Initialize tab if (tab = = NULL | |
(n = tab.length) = = 0) tab = inittable (); /** * CAs probe empty bucket * Calculates the array index in the bucket table where key is located: i = (n-1) & Hash) */else if ((f = tabat (tab, i = (n-1) &A mp hash) = = null) {//If the location does not have a value, CAs adds a new key-value pair and does not need to lock if (castabat (tab, I, NULL, new NODE< ; K,v> (hash, key, value, NULL))) break; No lock when adding to empty bin}//detected tab[i] bucket is in progress rehash, else if ((fh = f.hash) = = MOVED) tab =
Helptransfer (tab, f);
else {V oldval = null;
The first element of the bucket is locked exclusively synchronized (f) {if (Tabat (tab, i) = = f) {//bucket key value pairs organization form is linked list
if (FH >= 0) {bincount = 1;
for (node<k,v> e = f;; ++bincount) {K ek; If the hash value is the same as the key value, modify the value of the corresponding node if
(E.hash = = Hash && (ek = e.key) = = Key | | (ek! = null && key.equals (EK))))
{oldval = E.val;
Find the corresponding key-value pair, update the value if (!onlyifabsent) e.val = value;
Break
}//bucket does not have corresponding key-value pairs, inserted into the tail of the list node<k,v> pred = e;
if ((e = e.next) = = null) {Pred.next = new node<k,v> (hash, key,
value, NULL);
Break }}}//Bucket middle key value pair organization is red black tree else if (f instanceof treebin)
{ Node<k,v> p;
Bincount = 2; if (p = ((treebin<k,v>) f). Puttreeval (hash, key, value)) = null)
{oldval = P.val;
if (!onlyifabsent) p.val = value;
}}}}//Check the total number of key-value pairs in the bucket, if the list length has reached a critical value of 8, you need to convert the list to a tree structure if (bincount! = 0) {
if (bincount >= treeify_threshold)//list converted to red black tree treeifybin (tab, i);
if (oldval! = null) return oldval;
Break
}}}//Update Basecount Addcount (1L, Bincount);
return null;
}
Synchronized (f) {} operation by the first element of the bucket = linked table header Or Red-Black root node lock, so as to achieve the whole bucket lock, there is the idea of lock separation; gets the key-value pair: Get
public V get (Object key) {node<k,v>[] tab; Node<k,v> e, p; int n, eh;
K Ek;
Computes the hash value int h = spread (Key.hashcode ()); Determine the node position based on the hash value if (tab = table)! = null && (n = tab.length) > 0 && (E = tabat (tab
, (n-1) & h)) = null) {//If the Search node key is the same as the incoming key and is not NULL, return this node if (eh = e.hash) = = h) { if (ek = e.key) = = Key | |
(ek! = null && key.equals (EK)))
return e.val; }//If eh<0 indicates that this node is looking directly at the tree for else if (EH < 0) return (p = e.find (H, key))! = Nu ll?
P.val:null;
Otherwise traverse the list to find the corresponding value and return the while ((E = e.next)! = null) {if (E.hash = = h && (ek = e.key) = = Key | |
(ek! = null && key.equals (EK))))
return e.val;
}} return null; }
The Get method is relatively simple, given a key to determine the value of the time, must meet the two conditions--key the same, hash value is the same, for the node may be in the list or tree, the situation, you need to find separately. remove a key-value pair: Remove
The bottom of the Remove function is the invocation of the ReplaceNode function to implement the deletion of the node:
Final V ReplaceNode (object key, V value, Object CV) {//Calculate Key's hash value int hash = spread (Key.hashcode ()); For (node<k,v>[] tab = table;;)
{//infinite loop node<k,v> f; int n, I, FH; if (tab = = NULL | |
(n = tab.length) = = 0 | | (f = tabat (tab, i = (n-1) & hash) = = NULL)//table is empty or table length is 0 or the bucket corresponding to key is empty//jump out of loop b
Reak;
else if (fh = f.hash) = = MOVED)//The hash value of the first node in the bucket is MOVED//transfer tab = Helptransfer (tab, f);
else {V oldval = null;
Boolean validated = false; Synchronized (f) {//Locking synchronous if (tabat (tab, i) = = f) {//The first node in the bucket has not changed if (FH
>= 0) {//node hash value greater than 0 validated = true; for (node<k,v> e = f, pred = null;;)
{//infinite loop K ek; if (E.hash = = Hash && (ek = e.key) = = Key | | (ek! = null && key.equals (EK))))
The hash value of the {//node is equal to the specified hash value, and the key is also equal to V ev = E.val;
if (CV = = NULL | | cv = = EV | | (EV! = NULL && cv.equals (EV)))
{//CV is empty or equal to node value or not null and equal//Save the Val value of the node
Oldval = EV;
if (value = NULL)//value is NULL//Set node value value
E.val = value;
else if (pred! = null)//precursor NOT NULL//the successor of the precursor is the successor of E, that is, the e node is deleted
Pred.next = E.next;
Else Set the value for index in table tables to E.next settabat (tab, I, E.nex
T);
} break;
} pred = e;
if ((e = e.next) = = null) break;
}} else if (f instanceof treebin) {//For red-black tree node type
validated = true;
Type conversion treebin<k,v> t = (treebin<k,v>) F;
Treenode<k,v> R, p; if ((r = t.root) = null && (p = r.findtreenode (hash, key, null)) = = null) {//root section
The point is not empty and there is a node equal to the specified hash and key//the value V PV = p.val that holds the P node; if (CV = = NULL | | cv = = PV | | (PV! = null && cv.equals (PV)))
{//CV is null or equal to node value or not null and equal oldval = PV;
if (value = null) P.val = value;
else if (T.removetreenode (p))//Remove P-node settabat (tab, I, Untreeify (T.first));
}
}
}
} } if (validated)