Concurrenthashmap can achieve high-performance concurrent access.
Concurrenthashmap has an internal segment array, and each segment has a lock.
Segment is equivalent to a sub-map and has a hashentity array.
In this way, the concurrent pressure can be distributed to multiple segments.
Concurrenthashmap:
Here, the number of segments must be the nth power of 2. In order to efficiently calculate the segment on which the key to be stored (implemented by <).
Inside the segment, a hashentry array of volatile type is provided to allow other threads to see the latest values.
Concurrenthashmap has put, get, remove, and other operations. The original expansion operation only targets a single segment for performance.
The key segment is equivalent to the submap in concurrenthashmap, which contains a hashentry array. Its Class diagram is as follows:
The key segment class diagram is as follows:
Hashentry stores the hash value, final key value, volatile value, and next value.
Here the value and next values are volatile, which provides the foundation for implementing get operations without locking.
The hashentry class diagram is as follows:
The following are created from concurrenthashmap, and elements are put into concurrenthashmap, and elements are retrieved from concurrenthashmap for analysis.
1. Create concurrenthashmap
When using concurrenthashmap, You can input the desired map capacity, loadfactor load factor, and concurrency in the constructor (which roughly determines the number of segments ).
If this parameter is not input, when an empty constructor is used, the default map capacity is 16, the loadfactor is 0.75, And the concurrency is 16.
The analysis starts with the default parameters.
First, you need to determine the total capacity (this refers to the total size of the array elements stored in all segments), the total number of segments, and the size of the hashentry array in each segment.
We will first attach the Code:
public ConcurrentHashMap(int initialCapacity, float loadFactor, int concurrencyLevel) { if (!(loadFactor > 0) || initialCapacity < 0 || concurrencyLevel <= 0) throw new IllegalArgumentException(); if (concurrencyLevel > MAX_SEGMENTS) concurrencyLevel = MAX_SEGMENTS; // Find power-of-two sizes best matching arguments int sshift = 0; int ssize = 1; while (ssize < concurrencyLevel) { ++sshift; ssize <<= 1; } this.segmentShift = 32 - sshift; this.segmentMask = ssize - 1; if (initialCapacity > MAXIMUM_CAPACITY) initialCapacity = MAXIMUM_CAPACITY; int c = initialCapacity / ssize; if (c * ssize < initialCapacity) ++c; int cap = MIN_SEGMENT_TABLE_CAPACITY; while (cap < c) cap <<= 1; // create segments and segments[0] Segment<K,V> s0 = new Segment<K,V>(loadFactor, (int)(cap * loadFactor), (HashEntry<K,V>[])new HashEntry[cap]); Segment<K,V>[] ss = (Segment<K,V>[])new Segment[ssize]; UNSAFE.putOrderedObject(ss, SBASE, s0); // ordered write of segments[0] this.segments = ss; }
As you can see, for the number of segments, ssize takes the Npower of 2 with concurrencylevel as the upper bound. Here, because concurrencylevel is 16, ssize is also 16.
Then, segmentshift is calculated. This value is used to determine how many hash values are involved in the calculation of the segment array subscript of the stored elements when the elements are subsequently stored in concurrenthashmap.
Here, segmentshift is 32-sshift. Sshift is the N value of the N times of the number of segment ssize 2, that is, the number of shifts left when ssize is calculated. Here, it is 4.
So segmentshift is 28.
Then, the segmentmask is calculated. This value is used to obtain the subscript of the array where the segment storing elements is located. Here, we use the & operation to replace the % mode operation for efficiency.
Here, because the number of segment is 16, segmentmask is 15.
Then calculate the array capacity of each segment. Here, calculate the approximate size of each segment, that is, use int c = initialcapacity/ssize; To obtain c. Here C is 1.
A cap will be initialized as the hashentry array size in the real segment, and it will be initialized to 2.
Then, to ensure that the hashentry size in the segment is 2 to the Npower, the following operations will be performed on the CAP:
While (Cap <C)
Cap <= 1;
That is, if the cap value is not 2 (when the C value is less than or equal to 2), or if the cap value is greater than the Npower of 2.
After obtaining the size of the hashentry array in each segment, create a segment array and initialize a segment at the position 0 of the segment array.
Finally, set the segments of concurrenthashmap to the newly created segments array.
Here we can see that the last created segment S0 is to call unsafe. putorderedobject (SS, sbase, S0) to put it into the SS array.
Here, unsafe. putorderedobject is a method provided by Java for Direct Memory operations. The parameter SS is an array.
Unsafe. putorderedobject will delay update to the memory, but since the segment in the segment array is subsequently obtained, it uses unsafe. getobjectvolatile, so it can ensure that the operations to put the segment into the array are visible to subsequent threads.
Sbase is a static final long value of concurrenthashmap, equivalent to the first address of the segment array.
S0 is the segment instance created in the SS array. Put S0 into the SS array position 0.
If the following code is provided: unsafe. putorderedobject (SS, sbase + offset, S), the element S is placed in the sbase + offset position of the array ss.
Here, offset:
Offset = Step * The number of places in the array
Generally, the step size has been determined during the compilation period. For the values of double or integer, The step size is 4.