before HashMap As mentioned above, concurrency can cause a dead loop, which can be avoided by using Concurrent HashMap in multithreading.
Segment
Concurrent HashMap is composed of several Segments. Segments inherit ReentrantLock. Each lock is to one Segment without affecting other Segments, and achieves the function of lock separation (also called segmented lock).
Each Segment also contains an array of HashEntry, which is a linked list. As shown in the following figure:
Initialization
Initial Capacity: Initial capacity size, default 16.
Load Factor: Expansion factor, table expansion use, Segments does not expand. By default, 0.75, when the Segment capacity is greater than the initial Capacity * loadFactor, the expansion begins.
Concurrency Level: Concurrent number, default 16, directly affects the value of segmentShift and segmentMask, and the number of segments initialized. The number of Segments initialized is the nearest and greater value to the N power of 2, such as concurrencyLevel=16, Segments 16, concurrencyLevel=17, Segments 32. SegmentShift's value is like this, for example, Segment is 32, relative to the 5th power of 2, then its value is 32-5, 27, followed by unsigned right-shift 27 bits, that is, when 5 bits higher, it is 0-31 value, at this time, the subscript of Segment is also 0-31, which corresponds to each Segment after taking the module. SegmentMask is the n-th power-1 of 2, where n is 5 for modulus. The indexFor method of hashmap has been mentioned before.
When initializing, you also initialize the first Segment and the size of the table array in the Segment, which is greater than or equal to the number of initial Capacity divided by the Segment array, with an average allocation of 2 and the N th power of 2. For example, when initial Capacity is 32 and concurrency Level is 16, then the number of Segments is 16, 32 divided by 16, which is equal to 2. If initial Capacity is 33, Segments are 16, 33 divided by 16, 4.
public ConcurrentHashMap() { this(DEFAULT_INITIAL_CAPACITY, DEFAULT_LOAD_FACTOR, DEFAULT_CONCURRENCY_LEVEL); } 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;//Used in high position to determine which Segment to fall on this.segmentMask = ssize - 1;//Used to take the mould. The indexFor method of hashmap has been mentioned before. n-1 of 2 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]);//Initialize the Segment of the first location Segment<K,V>[] ss = (Segment<K,V>[])new Segment[ssize];//Initialize Segments UNSAFE.putOrderedObject(ss, SBASE, s0); // ordered write of segments[0] this.segments = ss; }
put method
public V put(K key, V value) { Segment<K,V> s; if (value == null) throw new NullPointerException(); int hash = hash(key); //Move right unsigned and take the module. Which Segment does it fall on? int j = (hash >>> segmentShift) & segmentMask; if ((s = (Segment<K,V>)UNSAFE.getObject // nonvolatile; recheck (segments, (j << SSHIFT) + SBASE)) == null) // in ensureSegment s = ensureSegment(j); return s.put(key, hash, value, false); }
ensureSegment method
Determine which Segment to fall on and initialize if it's empty, because the first Segment was initialized before.
private Segment<K,V> ensureSegment(int k) { final Segment<K,V>[] ss = this.segments; long u = (k << SSHIFT) + SBASE; // raw offset Segment<K,V> seg; if ((seg = (Segment<K,V>)UNSAFE.getObjectVolatile(ss, u)) == null) { //Initialization using segment[0] table length and loadFactor Segment<K,V> proto = ss[0]; // use segment 0 as prototype int cap = proto.table.length; float lf = proto.loadFactor; int threshold = (int)(cap * lf); HashEntry<K,V>[] tab = (HashEntry<K,V>[])new HashEntry[cap]; if ((seg = (Segment<K,V>)UNSAFE.getObjectVolatile(ss, u)) == null) { // recheck Segment<K,V> s = new Segment<K,V>(lf, threshold, tab); while ((seg = (Segment<K,V>)UNSAFE.getObjectVolatile(ss, u))//cas operation, only one set value succeeds, if other succeeds, assign value, and return == null) { if (UNSAFE.compareAndSwapObject(ss, u, null, seg = s)) break; } } } return seg; }
put method
final V put(K key, int hash, V value, boolean onlyIfAbsent) { HashEntry<K,V> node = tryLock() ? null : scanAndLockForPut(key, hash, value);//Getting Segment Locks V oldValue; try { HashEntry<K,V>[] tab = table; int index = (tab.length - 1) & hash;//Above is the hash to get the high position of the Segment, and here is the hash of the tabel. HashEntry<K,V> first = entryAt(tab, index);//Headers of arrays fetched to hash positions for (HashEntry<K,V> e = first;;) {//Traversing from the beginning to the end if (e != null) { K k; if ((k = e.key) == key || (e.hash == hash && key.equals(k))) {//The key is the same, or the hash value is the same oldValue = e.value; if (!onlyIfAbsent) {//Replacement or not e.value = value; ++modCount; } break; } e = e.next; } else { if (node != null)//Not empty, set to header node.setNext(first); else node = new HashEntry<K,V>(hash, key, value, first);/Place the header after initialization int c = count + 1; if (c > threshold && tab.length < MAXIMUM_CAPACITY) rehash(node);// Capacity expansion else setEntryAt(tab, index, node);//Place the new node on tab index ++modCount; count = c; oldValue = null; break; } } } finally { unlock();//Release lock } return oldValue; }
Scan AndLockForPut Method
Attempt to acquire locks without first initializing node s
private HashEntry<K,V> scanAndLockForPut(K key, int hash, V value) { HashEntry<K,V> first = entryForHash(this, hash);//There is null in the header node after hash HashEntry<K,V> e = first; HashEntry<K,V> node = null; int retries = -1; // negative while locating node while (!tryLock()) {//This put method first tries to get, but it can't. Here, while loop tries to get. HashEntry<K,V> f; // to recheck first below if (retries < 0) { if (e == null) {//When the node is empty if (node == null) // speculatively create node node = new HashEntry<K,V>(hash, key, value, null);//Initialize node retries = 0; } else if (key.equals(e.key))//When the head node is not empty retries = 0; else e = e.next; } else if (++retries > MAX_SCAN_RETRIES) {//Over the number of retries, enter the blocking queue and wait for the lock lock(); break; } else if ((retries & 1) == 0 && (f = entryForHash(this, hash)) != first) {//It's not first, it's already got other nodes in. e = first = f; // re-traverse if entry changed retries = -1; } } return node; }
rehash method, expansion, table expansion
private void rehash(HashEntry<K,V> node) { HashEntry<K,V>[] oldTable = table; int oldCapacity = oldTable.length; int newCapacity = oldCapacity << 1;//Move left, twice as much as before. threshold = (int)(newCapacity * loadFactor); HashEntry<K,V>[] newTable = (HashEntry<K,V>[]) new HashEntry[newCapacity]; int sizeMask = newCapacity - 1; for (int i = 0; i < oldCapacity ; i++) { HashEntry<K,V> e = oldTable[i]; if (e != null) { HashEntry<K,V> next = e.next; int idx = e.hash & sizeMask; if (next == null) // Empty, no later nodes, directly to the new array newTable[idx] = e; else { // Reuse consecutive sequence at same slot HashEntry<K,V> lastRun = e; int lastIdx = idx; //Because the array is twice as large as the previous index, after hash, it either falls in the same position as the previous index or adds the value of old Capacity. //For example, capacity is 2, expansion 4, hash is 2, 4, 6, 10, 14, then the latter three are divided by 4 more than 2, can be directly copied. for (HashEntry<K,V> last = next; last != null; last = last.next) { int k = last.hash & sizeMask; if (k != lastIdx) {//hash is different, again lastIdx = k; lastRun = last; } } //To execute the above, lastRun is 6, 10, 14 newTable[lastIdx] = lastRun;//Above // Clone remaining nodes when cloning, encounter lastrun, directly based on the so-called value, but the previous possible index is the same as lastrun, such as 2 for (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); } } } } int nodeIndex = node.hash & sizeMask; // add the new node node.setNext(newTable[nodeIndex]);//Join the End Node newTable[nodeIndex] = node; table = newTable; }
get method
public V get(Object key) { Segment<K,V> s; // manually integrate access methods to reduce overhead HashEntry<K,V>[] tab; int h = hash(key); long u = (((h >>> segmentShift) & segmentMask) << SSHIFT) + SBASE; if ((s = (Segment<K,V>)UNSAFE.getObjectVolatile(segments, u)) != null &&//Find Segment, logical same as put (tab = s.table) != null) { for (HashEntry<K,V> e = (HashEntry<K,V>) UNSAFE.getObjectVolatile (tab, ((long)(((tab.length - 1) & h)) << TSHIFT) + TBASE);//Find the table, logic and put e != null; e = e.next) {//Traversal table K k; if ((k = e.key) == key || (e.hash == h && key.equals(k))) return e.value; } } return null; }