70% do not know why Java programmers do not need to read ConcurrentHashMap lock?

Author: God love to eat apples

table of Contents

About 1.ConcurrentHashMap of
2.get operation source
3.volatile stage
4 is added to the array of volatile?
5. volatile modified the Node
6. summary

We know, ConcurrentHashmap (1.8) The concurrent collections framework is thread safe, when you get to see the source of the operation, you will find get full operation is not added any lock, which is the problem discussed in this blog - why it You do not need to lock it?

Introduction of ConcurrentHashMap
I want to have students know basis in jdk1.7 is the use of Segment + HashEntry + ReentrantLock way to achieve, but gave up 1.8 Segment bloated design, replaced by the introduction Node + CAS + Synchronized to ensure concurrency security for implementation.
JDK1.8 achieve reduction lock granularity, JDK1.7 version lock granularity is based on the Segment, comprising a plurality of HashEntry, the lock granularity is JDK1.8 HashEntry (head node)
JDK1.8 version of the data structure to become more simple, so that the operation is more clear and smooth, because it has been used to synchronize synchronized, so that the concept does not need to segment the lock, there is no need segment this data structure, because the size reduction, the implementation complexity also increases
JDK1 .8 red-black tree to optimize the use of the list, based on the length of a long chain traversal is a very long process, and the efficiency of the red-black tree traversal is fast, instead of the list of predetermined threshold values, so that the best partner to form a
70% do not know why Java programmers do not need to read ConcurrentHashMap lock?

get operation source
is first calculated hash value, navigate to the location of the index table, if the node is the first in line to return
if they are time expansion, will call signs node ForwardingNode the find method is expansion and look for the node, match returns
None of the above in line, then it is down to traverse the node, match returns, otherwise it returns null final

//会发现源码中没有一处加了锁
public V get(Object key) {
 Node<K,V>[] tab; Node<K,V> e, p; int n, eh; K ek;
 int h = spread(key.hashCode()); //计算hash
 if ((tab = table) != null && (n = tab.length) > 0 &&
 (e = tabAt(tab, (n - 1) & h)) != null) {//读取首节点的Node元素
 if ((eh = e.hash) == h) { //如果该节点就是首节点就返回
 if ((ek = e.key) == key || (ek != null && key.equals(ek)))
 return e.val;
 }
 //hash值为负值表示正在扩容,这个时候查的是ForwardingNode的find方法来定位到nextTable来
 //eh=-1,说明该节点是一个ForwardingNode,正在迁移,此时调用ForwardingNode的find方法去nextTable里找。
 //eh=-2,说明该节点是一个TreeBin,此时调用TreeBin的find方法遍历红黑树,由于红黑树有可能正在旋转变色,所以find里会有读写锁。
 //eh>=0,说明该节点下挂的是一个链表,直接遍历该链表即可。
 else if (eh < 0)
 return (p = e.find(h, key)) != null ? p.val : null;
 while ((e = e.next) != null) {//既不是首节点也不是ForwardingNode,那就往下遍历
 if (e.hash == h &&
 ((ek = e.key) == key || (ek != null && key.equals(ek))))
 return e.val;
 }
 }
 return null;
}

get not locked, then, ConcurrentHashMap is how to ensure that data is not read dirty data in it?
volatile stage
for visibility, Java provides the volatile keyword to ensure visibility, orderly. But it does not guarantee atomicity.
Ordinary share variables can not guarantee the visibility, because after a normal shared variables are modified, and when is written to main memory is uncertain when the other threads to read at this time may still be the original value of the old memory, making it impossible ensure visibility.
The volatile keyword can be consistent in the subsequent reading of multiple threads of the basic types of changes, but for reference types such as arrays, entity bean, only to ensure the visibility of reference, but does not guarantee the visibility of reference content. .
Prohibition command reordering.
Background: In order to increase the processing speed, memory, and processor does not communicate directly, but first the system memory to read data internal cache (L1, L2, or other) before the operation, but the operation is not finished is written to know when RAM.
If you declare a variable volatile write operation is performed, JVM will send a command to the processor, where the variable data cache line is written back to system memory. However, even if written back to memory if the other processor cache or old values, then perform computing operations will be a problem.
In multi-processor, in order to ensure each processor cache is consistent, it will cache coherency protocol, when a CPU when writing data, if found operating variable is a shared variable, it will notify the other CPU to inform the variable cache line is invalid and therefore the other when the CPU reads the variable is found to be invalid reload the data from main memory.
70% do not know why Java programmers do not need to read ConcurrentHashMap lock?

To conclude:
a first: use the volatile keyword will force the modified values immediately written to main memory;
Second: the use of the volatile keyword, then, the thread 2 when modified, causes the cache in the working memory of the thread 1 cache variable invalid line (reflecting layer to the hardware, then, it is L1 or L2 cache in the CPU cache line corresponding to invalid);
third: the working memory due to an invalid cache line in the thread 1 cache variable, the value of the variable thread 1 read again when will go to the main memory read.

It is added to the array of volatile?

/**
     * The array of bins. Lazily initialized upon first insertion.
     * Size is always a power of two. Accessed directly by iterators.
     */
 transient volatile Node<K,V>[] table;

We know that volatile array can be modified, but different meaning and it looks like on the surface. For chestnut, volatile int array [10] refers to the value of array addresses is not volatile and volatile elements of the array.

Volatile modified with the Node
GET operation may be due to no lock val element and the next Node pointer is that volatile, node A new thread or modified val nodes in a multi-threaded environment when the thread B is visible.

static class Node<K,V> implements Map.Entry<K,V> {
 final int hash;
 final K key;
 //可以看到这些都用了volatile修饰
 volatile V val;
 volatile Node<K,V> next;

 Node(int hash, K key, V val, Node<K,V> next) {
 this.hash = hash;
 this.key = key;
 this.val = val;
 this.next = next;
 }

 public final K getKey() { return key; }
 public final V getValue() { return val; }
 public final int hashCode() { return key.hashCode() ^ val.hashCode(); }
 public final String toString(){ return key + "=" + val; }
 public final V setValue(V value) {
 throw new UnsupportedOperationException();
 }

 public final boolean equals(Object o) {
 Object k, v, u; Map.Entry<?,?> e;
 return ((o instanceof Map.Entry) &&
 (k = (e = (Map.Entry<?,?>)o).getKey()) != null &&
 (v = e.getValue()) != null &&
 (k == key || k.equals(key)) &&
 (v == (u = val) || v.equals(u)));
 }

 /**
     * Virtualized support for map.get(); overridden in subclasses.
     */
 Node<K,V> find(int h, Object k) {
 Node<K,V> e = this;
 if (k != null) {
 do {
                K ek;
 if (e.hash == h &&
 ((ek = e.key) == k || (ek != null && k.equals(ek))))
 return e;
 } while ((e = e.next) != null);
 }
 return null;
 }
}

Since the volatile modification to get an array operation has no effect What added to the array of volatile purpose is it?
In fact, just to make Node array with visibility and added to other threads when the volatile expansion

Summary
ConcurrentHashMap in 1.8 do not need to get the whole operation lock, this is it other than the concurrent collections such as hashtable, with Collections.synchronizedMap () packaging hashmap; one reason for the high security efficiency.
get the whole operation does not need to be locked because the members of Node val is volatile and modified arrays nothing to do with the volatile modification.
Array with volatile modification is mainly to ensure that when the expansion of the array to ensure visibility.

Welcome to share with everyone, like the point of a praise yo remember the article, thanks for the support!

Guess you like

Origin blog.51cto.com/14442094/2427406