transferred from: http://cache.baiducontent.com/ idyllic scenes represent day
In concurrent programming practice, CONCURRENTHASHMAP is a data structure that is often used, compared to Hashtable and Collections.synchronizedmap (), Concurrenthashmap provides better write concurrency based on thread security, but also reduces the need for read consistency (this is like the cap theory O (∩_∩) o). The design and implementation of CONCURRENTHASHMAP is very sophisticated, using a lot of volatile, Final,cas and other lock-free technology to reduce the impact of lock competition on performance, Regardless of the degree of Java Concurrency Programming or the understanding of Java memory model, Concurrenthashmap design and source code are worth very careful reading and guessing.
This log records some of their own understanding of Concurrenthashmap, based on the JDK1.7 source code (Concurrenthashmap in JDK 1.6, 1.7, and the latest version 1.8).
Design ideas
Concurrenthashmap adopts the design of segmented lock, only in the same segment there is a race relationship, there is no lock competition between different segment locks. Compared with the whole map lock design, the segmented lock greatly improves the processing power in the high Concurrency environment. But at the same time, because the entire map is not locked, some methods that need to scan the entire map (such as size (), containsvalue ()) need to use a special implementation, and some other methods (such as clear ()) even discard the requirement for consistency.
A segmented lock in Concurrenthashmap is called a segment, which is a structure similar to HashMap, which has a entry array inside, each element in the array is a linked list, and a reentrantlock. The hashentry in Concurrenthashmap is somewhat different from the entry in HashMap: The value in Hashentry and the next are modified by volatile , This allows them to maintain visibility during multithreaded read and write, with the following code:
Static Final class hashentry<k, v> { final int hash; Final K key; volatile V value; volatile Hashentry<k, v> next;}
Concurrency (Concurrency level)
The degree of concurrency can be understood as the maximum number of threads that can update conccurenthashmap at the same time and not generate lock contention, which is actually the array length of the number of segmented locks in concurrenthashmap, or segment[]. Concurrenthashmap The default concurrency is 16, but the user can also set the concurrency level in the constructor. When the user sets the concurrency level, Concurrenthashmap uses the minimum 2 power exponent greater than or equal to the value as the actual concurrency (assuming the user sets the concurrency to 17, the actual concurrency is 32). The runtime locates the segment by positioning and computing the high n bits (n = 32–segmentshift) and the concurrency minus 1 (segmentmask) of the key. Both Segmentshift and Segmentmask are calculated according to the concurrency level in the construction process.
If the concurrency setting is too small, it can lead to serious lock contention, and if the concurrency setting is too large, the access in the same segment will spread to different segment, and the CPU cache hit rate will decrease, which can cause program performance to degrade.
Create a segment lock
Unlike JDK1.6, the remainder of the segments, except for the first segment, uses the mechanism of lazy initialization: before each put, it is necessary to check if the segment of the key corresponds to NULL, and if yes, ensuresegment () To ensure that the corresponding segment are created.
Ensuresegment may be called in a concurrent environment, but unlike the imagination, Ensuresegment does not use locks to control competition, but instead uses the getobjectvolatile of unsafe objects () Provides atomic read semantics in conjunction with CAS to ensure the atomicity of segment creation. The code snippet is as follows:
if (seg = (segment<k,v>) unsafe.getobjectvolatile (ss, u)) = = null) {//recheck segment<k,v> s = new S Egment<k,v> (LF, threshold, tab); while (seg = (segment<k,v>) unsafe.getobjectvolatile (ss, u)) = = null) { if (unsafe.compareandswapobject (ss, U, NULL, SEG = s)) Break;}}}
Put/putifabsent/putall
Like JDK1.6, the put method of Concurrenthashmap is represented in the corresponding segment (described before the principle of positioning segment). Unlike JDK1.6, the 1.7 version of Concurrenthashmap in the process of acquiring segment lock, did a certain optimization-?0?2 before actually applying for the lock, the put method will try to obtain the lock through the Trylock () method, The linked list of the corresponding hashcode is traversed during the attempt to acquire the lock, and if the traversal is still not found for the same Hashentry node as the key, a hashentry is created in advance for the subsequent put operation. When a lock is still not available after a certain number of trylock, the lock is applied through lock.
It is important to note that, because in a concurrent environment, the Put,rehash or remove operations of other threads may result in changes in the node nodes of the chain, so checks are required during the process, and if the head node changes, the table is re-traversed. And if other threads cause a node in the list to be deleted, even if the change is due to a non-atomic write operation (the Unsafe.putorderedobject () is called by the subsequent node after the node is deleted, the method does not provide atomic write semantics) may cause the current thread to be unable to observe. However, it is negligible because it does not affect the correctness of the traversal.
The reason to traverse the entire linked list during the acquisition of a lock is to expect the linked list to be cached by the CPU cache and to improve the performance of the linked list traversal operation in the subsequent actual put process.
After the lock is acquired, the segment iterates over the linked list, and if a hashentry node has the same key, the value of the hashentry is updated, otherwise a hashentry node is created. Set it to the new head node of the list and set the original head node to the next node of the new head. If the total number of nodes (with new hashentry) exceeds threshold during the new process, call the rehash () method to expand the segment and finally write the new hashentry to the array.
In the Put method, the next node linking the new node (Hashentry.setnext ()) and writing the list to the array (Setentryat ()) are implemented through the unsafe Putorderedobject () method. The reason why Putobjectvolatile () with atomic write semantics is not used here is that JMM guarantees that the status updates for all objects between locks and release locks are updated to main memory after the lock is released, ensuring that the changes are visible to other threads.
Rehash
The rehash principle of resize,concurrenthashmap is similar to that of HashMap, but Doug Lea has made some optimizations for rehash to avoid having all nodes replicate: Because the expansion is based on the power of 2, Assuming that a hashentry corresponds to the index of the array in the segment before the expansion, the capacity of the array is capacity, then the index of the hashentry corresponding to the new array can only be I or i+capacity after the expansion, Therefore, most hashentry nodes can remain unchanged before and after the expansion. Based on this, the rehash method locates the node that the first successive nodes will remain unchanged after the expansion, and then re-arrange all the nodes before the node. This part of the code is as follows:
hashentry<k,v> Lastrun = E;int Lastidx = idx;for (hashentry<k,v> last = next; Last! = null; Last = last.next) { int k = Last.hash & sizemask; if (k! = lastidx) { lastidx = k; Lastrun = Last; }} NEWTABLE[LASTIDX] = lastrun;//Clone remaining nodesfor (hashentry<k,v> p = e; P! = Lastrun; p = p.next) { V v = P.value; int h = p.hash; int k = h & sizemask; hashentry<k,v> n = newtable[k]; NEWTABLE[K] = new Hashentry<k,v> (h, P.key, V, n);}
Remove
Similar to put, remove loops the linked list to increase the cache hit rate before actually acquiring the lock.
Get and ContainsKey
get is almost exactly the same as the ContainsKey two methods: they do not use locks, but rather the atomic read semantics provided by the Getobjectvolatile () method of the unsafe object to obtain segment and the corresponding linked list, The linked list is then traversed to determine if there is a node with the same key and the value of the node is obtained. However, because other threads in the traversal process may have adjusted the list structure, get and ContainsKey may return obsolete data, which is the embodiment of concurrenthashmap on weak consistency. If strong consistency is required, then the Collections.synchronizedmap () method must be used.
Size, Containsvalue
These methods are all based on the entire concurrenthashmap, and their principle is basically similar: first does not lock the loop to do the following: Loop all the segment (through the unsafe getobjectvolatile () to ensure the atomic read semantics), Obtain the corresponding value and the sum of all segment modcount. If the modcount and equality of all segment occur twice in a row, no other thread modifies concurrenthashmap in the process, and the obtained value is returned.
When the number of loops exceeds a predefined value, all segment are then locked sequentially, the return value is retrieved, and then the lock is unlocked in turn. It is worth noting that the lock process to force the creation of all segment, otherwise prone to other threads to create segment and Put,remove and other operations. The code is as follows:
for (int j = 0; j < segments.length; ++j) Ensuresegment (j). Lock ();//Force creation
In general, you should avoid using the size and Containsvalue methods in a multithreaded environment.
Note 1:modcount will be modified in put, replace, remove, and clear methods.
Note 2: For the Containsvalue method, if Hashentry matching value is found during the loop, it returns true directly.
Finally, unlike HashMap, Concurrenthashmap does not allow key or value to be null, and according to Doug Lea, the reason for this design is that in Concurrenthashmap, once value appears null, It means that the key/value of Hashentry is seen by other threads without mapping, and requires special handling. In JDK1.6, there is a defensive judgment of hashentry.value = = NULL in the implementation of theget method. But Doug Lea also admits that this situation seems unlikely to occur during the actual operation (ref.: http://cs.oswego.edu/pipermail/concurrency-interest/2011-March/007799.html).
Concurrency of Concurrenthashmap