ConcurrentHashMap source code analysis
CocurrentHashMap uses HashTable to implement concurrent hash search and storage by locking the entire table. CocurrentHashMapt can implement the same functions through Segment, but it is more efficient. In jdk1.6, cocuentHashMap has a weak consistency problem, but this problem has been fixed at jdk1.7. Therefore, both concurrency security and performance are very high. Next I will try to analyze CocurrentHashMap Based on the jdk1.7 source code.
CocurrentHashMap initialization preprocessing
// Unsafe mechanics private static final sun.misc.Unsafe UNSAFE; private static final long SBASE; private static final int SSHIFT; private static final long TBASE; private static final int TSHIFT; static { int ss, ts; try { UNSAFE = sun.misc.Unsafe.getUnsafe(); Class
tc = HashEntry[].class; Class
sc = Segment[].class; TBASE = UNSAFE.arrayBaseOffset(tc); SBASE = UNSAFE.arrayBaseOffset(sc); ts = UNSAFE.arrayIndexScale(tc); ss = UNSAFE.arrayIndexScale(sc); } catch (Exception e) { throw new Error(e); } if ((ss & (ss-1)) != 0 || (ts & (ts-1)) != 0) throw new Error(data type scale not a power of two); SSHIFT = 31 - Integer.numberOfLeadingZeros(ss); TSHIFT = 31 - Integer.numberOfLeadingZeros(ts); }
Code parsing: first, obtaining Unsafe provides cas operations, and java underlying multi-thread concurrency is completed through cas. However, cas operations still have some problems for high-precision concurrency. [For this issue, we will analyze it later ]. UNSAFE. arrayBaseOffset (tc) and UNSAFE. arrayBaseOffset (SC) are both used to calculate the memory offset value of the HashEntry and Segment object to the array object. This is the value that must be obtained for the cas operation. Note:
// Get the offset of the first element in the array (get offset of a first element in the array) public native int arrayBaseOffset (java. lang. class aClass); // get the size of an element in the array (get size of an element in the array) public native int arrayIndexScale (java. lang. class aClass );
CocurrentHashMap Initialization
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
s0 = new Segment
(loadFactor, (int)(cap * loadFactor), (HashEntry
[])new HashEntry
[cap]); Segment
[] ss = (Segment
[])new Segment
[ssize]; UNSAFE.putOrderedObject(ss, SBASE, s0); // ordered write of segments[0] this.segments = ss; }
Code parsing: the above Code mainly serves to initialize the Segment [] array object and Segment object. Parse to analyze the most important put and get methods.
Put Method
public V put(K key, V value) { Segment
s; if (value == null) throw new NullPointerException(); int hash = hash(key.hashCode()); int j = (hash >>> segmentShift) & segmentMask; if ((s = (Segment
)UNSAFE.getObject // nonvolatile; recheck (segments, (j << SSHIFT) + SBASE)) == null) // in ensureSegment s = ensureSegment(j); return s.put(key, hash, value, false); }
Code parsing: Calculate the hash value of the key first, and then use this hash value to get the Segment object. Then the Segment object executes the put method. In this way, the put operation is completed. Since this process is very important, we certainly want to know how it handles concurrency and internal implementation.
EnsureSegment
private Segment
ensureSegment(int k) { final Segment
[] ss = this.segments; long u = (k << SSHIFT) + SBASE; // raw offset Segment
seg; if ((seg = (Segment
)UNSAFE.getObjectVolatile(ss, u)) == null) { Segment
proto = ss[0]; // use segment 0 as prototype int cap = proto.table.length; float lf = proto.loadFactor; int threshold = (int)(cap * lf); HashEntry
[] tab = (HashEntry
[])new HashEntry
[cap]; if ((seg = (Segment
)UNSAFE.getObjectVolatile(ss, u)) == null) { // recheck Segment
s = new Segment
(lf, threshold, tab); while ((seg = (Segment
)UNSAFE.getObjectVolatile(ss, u)) == null) { if (UNSAFE.compareAndSwapObject(ss, u, null, seg = s)) break; } } } return seg; }
First, the offset is calculated, and then the object is obtained using UnSafe. Here, you may be confused about how to obtain the offset value. Here, I also analyze the offset to obtain the value of long u = (k < Segment-> put
final V put(K key, int hash, V value, boolean onlyIfAbsent) { HashEntry
node = tryLock() ? null : scanAndLockForPut(key, hash, value); V oldValue; try { HashEntry
[] tab = table; int index = (tab.length - 1) & hash; HashEntry
first = entryAt(tab, index); for (HashEntry
e = first;;) { if (e != null) { K k; if ((k = e.key) == key || (e.hash == hash && key.equals(k))) { oldValue = e.value; if (!onlyIfAbsent) { e.value = value; ++modCount; } break; } e = e.next; } else { if (node != null) node.setNext(first); else node = new HashEntry
(hash, key, value, first); int c = count + 1; if (c > threshold && tab.length < MAXIMUM_CAPACITY) rehash(node); else setEntryAt(tab, index, node); ++modCount; count = c; oldValue = null; break; } } } finally { unlock(); } return oldValue; }
Get ()
public V get(Object key) { Segment
s; // manually integrate access methods to reduce overhead HashEntry
[] tab; int h = hash(key.hashCode()); long u = (((h >>> segmentShift) & segmentMask) << SSHIFT) + SBASE; if ((s = (Segment
)UNSAFE.getObjectVolatile(segments, u)) != null && (tab = s.table) != null) { for (HashEntry
e = (HashEntry
) UNSAFE.getObjectVolatile (tab, ((long)(((tab.length - 1) & h)) << TSHIFT) + TBASE); e != null; e = e.next) { K k; if ((k = e.key) == key || (e.hash == h && key.equals(k))) return e.value; } } return null; }
The principle is very simple. First, locate the segment and then the object. GetObjectVolatie ensures that the latest data can be read.
Conclusion: concurrentHashMap involves a lot of multi-threaded knowledge and java Memory Model knowledge. If you do not have enough capabilities, do not imitate them, but we can learn its thoughts and how it is implemented.