ConcurrentHashMap Analysis Based on JDK1.8

Source: Internet
Author: User

ConcurrentHashMap Analysis Based on JDK1.8

I have read the analysis of ConcurrentHashMap before, and I feel like I know more about it. But I received an interview last night and asked me to explain all the ConcurrentHashMap I knew.

Then I am stuttering, and I should be able to analyze it without any accident. Today, I am determined to analyze this omnipotent package, ConcurrentHashMap

Analyze ConcurrentHashMap in several aspects:

  • Put Method
  • Remove Method
  • Get Method

(1) put Method

Public V put (K key, V value ){
Return putVal (key, value, false );
}

The putVal method is called and three parameters are passed in. The first is key, the second is val, and the third is onlyIfAbsent (meaning: if true, if the inserted key is the same, the val value is not replaced. The default value is false, replace the latest val value)

There are many putVal methods. Let's talk about them in two parts:

// Part 1
Final V putVal (K key, V value, boolean onlyIfAbsent ){
// Determine the validity of input parameters
If (key = null | value = null) throw new NullPointerException ();
// The hash value corresponding to the computing key
Int hash = spread (key. hashCode ());
Int binCount = 0;
For (Node <K, V> [] tab = table ;;){
Node <K, V> f; int n, I, fh;
// If the hash table has not been initialized, initialize it
If (tab = null | (n = tab. length) = 0)
Tab = initTable ();
// Locate the index location of the hash Array Based on the hash value of the key
// If it is null, add a node to the location using CAS lockless
Else if (f = tabAt (tab, I = (n-1) & hash) = null ){
If (casTabAt (tab, I, null,
New Node <K, V> (hash, key, value, null )))
Break;
}

In the fourth row, if both the key and val values are null, an exception is thrown directly. Therefore, neither key nor val can be null.

The second note is to hash this function.

Static final int spread (int h ){
Return (h ^ (h >>> 16) & HASH_BITS;
}

I have introduced it in HashMap. I will not elaborate on it. It is very important.

The third note is to initialize the initTable function.

Private final Node <K, V> [] initTable (){
Node <K, V> [] tab; int SC;
// Initialization is performed if the table is empty.
While (tab = table) = null | tab. length = 0 ){
// If sizeCtl is smaller than zero, a thread is initializing.
// The current thread should discard CPU usage
If (SC = sizeCtl) <0)
Thread. yield (); // lost initialization race; just spin
// Otherwise, it indicates that no thread has initialized the table, so this thread will do this work.
Else if (U. compareAndSwapInt (this, SIZECTL, SC,-1 )){
// For the sake of insurance, check whether the following table is empty again
Try {
If (tab = table) = null | tab. length = 0 ){
// If SC is greater than zero, the capacity has been initialized. Otherwise, the default capacity is used.
Int n = (SC> 0 )? SC: DEFAULT_CAPACITY;
@ SuppressWarnings ("unchecked ")
// Construct an array based on capacity
Node <K, V> [] nt = (Node <K, V> []) new Node <?,?> [N];
Table = tab = nt;
// Calculate the threshold, equivalent to n * 0.75
SC = n-(n >>> 2 );
}
} Finally {
// Set the threshold
SizeCtl = SC;
}
Break;
}
}
Return tab;
}

We can see lines 7 and 8. If a thread is initializing, wait and let out the cpu.

Initialization only allows one thread to initialize the table. If other threads come in, other threads will be handed over the CPU to wait for the next system scheduling. This ensures that the table is initialized by only one thread at the same time.

The default value is 16 for initialization, and the period value is 0.75.

Let's go back to the putVal method.

// It is detected that the bucket node is of the ForwardingNode type to assist in expansion.
Else if (fh = f. hash) = MOVED)
Tab = helpTransfer (tab, f );
// The bucket node is a common node. It locks the bucket header node and tries to add a node at the end of the linked list.
Else {
V oldVal = null;
Synchronized (f ){
If (tabAt (tab, I) = f ){
// Add elements to a common linked list.
If (fh> = 0 ){
BinCount = 1;
For (Node <K, V> e = f; ++ binCount ){
K ek;
If (e. hash = hash & (ek = e. key) = key | (ek! = Null & key. equals (ek )))){
OldVal = e. val;
If (! OnlyIfAbsent)
E. val = value;
Break;
}
Node <K, V> pred = e;
If (e = e. next) = null ){
Pred. next = new Node <K, V> (hash, key, value, null );
Break;
}
}
}
// Add elements to the red/black tree. The hash Value of the TreeBin node is TREEBIN (-2)
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;
}
}
}
}
// BinCount! = 0 indicates that a node is successfully added or modified to the linked list or red/black tree.
// BinCount = 0 indicates that the put Operation adds a new node to the first node of a bucket.
If (binCount! = 0 ){
// The depth of the linked list exceeds 8 to convert it to the red/black tree
If (binCount> = TREEIFY_THRESHOLD)
TreeifyBin (tab, I );
// OldVal! = Null indicates that this operation is a modification operation.
// Directly return the old value without performing the following expansion boundary check
If (oldVal! = Null)
Return oldVal;
Break;
}
}
}
// Update baseCount in CAS mode and determine whether to resize
AddCount (1L, binCount );
// This step indicates that this put operation is an add operation. Otherwise, return returns
Return null;

We can see the third line. If the hash of f is MOVED, it will help him resize (it indicates that at least one thread is resizing)

To tell the truth, I don't quite understand this method. I checked some information on the Internet:

First, each thread receives its own job range first, and then starts -- I to traverse its job range and process each bucket. If the header node of the bucket is empty, use ForwardingNode to identify that the bucket has been processed. If a bucket has been processed, skip the next bucket. If the bucket is normal, lock the first node of the bucket and perform normal migration. After the migration is complete, the Location Identification Position of the original table will still be processed.

When I <0, it indicates that the processing speed of this thread is fast enough, and the last part of the entire table has been processed by it, now you need to check whether there are other threads still migrating in their own segments.

The code after putVal is clear. If it is a linked list, find the end node and insert it. If it is a red/black tree, insert the nursery rhyme.

So far, the source code analysis of the put method has completely ended, and it feels really complicated. At this point, I feel that I have not fully understood what each line of code means.

(2) remove Method

Public V remove (Object key ){
Return replaceNode (key, null, null );
}

Three parameters: the first parameter is the key, and the second parameter is the val parameter. The deletion parameter is directly set to null for gc to recycle. The third is the Object cv. The meaning is not clear yet. Let's continue.

There are two parts:

// Part 1
Final V replaceNode (Object key, V value, Object cv ){
// First find the key position hash
Int hash = spread (key. hashCode ());
For (Node <K, V> [] tab = table ;;){
Node <K, V> f; int n, I, fh;
// If the table is empty, null is returned directly.
If (tab = null | (n = tab. length) = 0 |
(F = tabAt (tab, I = (n-1) & hash) = null)
Break;
// If there is a resizing thread, help him resize
Else if (fh = f. hash) = MOVED)
Tab = helpTransfer (tab, f );

First, traverse the bucket node of the entire table. If the table is not initialized or cannot be located based on the hash value of the parameter, null is returned.

If the located bucket node type is ForwardingNode, call helpTransfer to help resize.

Else {
V oldVal = null;
Boolean validated = false;
Synchronized (f ){
If (tabAt (tab, I) = f ){
If (fh> = 0 ){
Validated = true;
For (Node <K, V> e = f, pred = null ;;){
K ek;
If (e. hash = hash &&
(Ek = e. key) = key |
(Ek! = Null & key. equals (ek )))){
V ev = e. val;
If (cv = null | cv = ev |
(Ev! = Null & cv. equals (ev ))){
OldVal = ev;
If (value! = Null)
E. val = value;
Else if (pred! = Null)
Pred. next = e. next;
Else
SetTabAt (tab, I, e. next );
}
Break;
}
Pred = e;
If (e = e. next) = null)
Break;
}
}
Else if (f instanceof TreeBin ){
Validated = true;
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 ){
V pv = p. val;
If (cv = null | cv = pv |
(Pv! = Null & cv. equals (pv ))){
OldVal = pv;
If (value! = Null)
P. val = value;
Else if (t. removeTreeNode (p ))
SetTabAt (tab, I, untreeify (t. first ));
}
}
}
}
}
If (validated ){
If (oldVal! = Null ){
If (value = null)
AddCount (-1L,-1 );
Return oldVal;
}
Break;
}
}

There are a lot of code, but I think it is not difficult to think about it. The lock is directly applied after the bucket is found. Determine whether it is a linked list or a tree, and then delete it.

(3) get Method

Public V get (Object key ){
Node <K, V> [] tab; Node <K, V> e, p; int n, eh; K ek;
Int h = spread (key. hashCode ());
If (tab = table )! = Null & (n = tab. length)> 0 &&
(E = tabAt (tab, (n-1) & h ))! = Null ){
If (eh = e. hash) = h ){
If (ek = e. key) = key | (ek! = Null & key. equals (ek )))
Return e. val;
}
Else if (eh <0)
Return (p = e. find (h, key ))! = Null? P. val: null;
While (e = e. next )! = Null ){
If (e. hash = h &&
(Ek = e. key) = key | (ek! = Null & key. equals (ek ))))
Return e. val;
}
}
Return null;
}

Similar to the get method of HashMap. No concurrent operations are involved. Get the hash value of the key directly. If it is the first node, return directly. Otherwise, the while loop is searched.

This article permanently updates link: https://www.bkjia.com/Linux/2018-02/151111.htm

Contact Us

The content source of this page is from Internet, which doesn't represent Alibaba Cloud's opinion; products and services mentioned on that page don't have any relationship with Alibaba Cloud. If the content of the page makes you feel confusing, please write us an email, we will handle the problem within 5 days after receiving your email.

If you find any instances of plagiarism from the community, please send an email to: info-contact@alibabacloud.com and provide relevant evidence. A staff member will contact you within 5 working days.

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.