The change of concurrent HashMap from Java 7 to Java 8

Keywords: Java

1, About segmented locks

The collection framework greatly reduces the repetitive work of Java programmers. In Java multithreading environment, using collection classes in a thread safe way is a first consideration.

In the hash table that can ensure thread safety, ConcurrentHashMap is well known, and it is also known that it uses segmented locks internally. However, with the rapid development of Java language, segmented locking has become a history.

In Java 8, you don't see segmented locks. In Java 7, Segment inherits from ReentrantLock and uses display lock. In Segment's instance method, each update operation uses Unsafe to process the update internally. This is clearly a waste. Both display lock and Unsafe can guarantee atomic operation on objects. Just use one.

The data in Java7 is stored in the array final Segment < K, V > [] segments; this is a specific size Segment array,

Segment inherits from ReentrantLock, so display lock can be used during update operation.

2, Internal class Segment

 static final class Segment<K,V> extends ReentrantLock implements Serializable {
        private static final long serialVersionUID = 2249069246763182397L;
        // tryLock() maximum wait time
        static final int MAX_SCAN_RETRIES =
            Runtime.getRuntime().availableProcessors() > 1 ? 64 : 1;
        // Segmented table, elements provide instantaneous operations through the entryAt/setEntryAt methods.
        transient volatile HashEntry<K,V>[] table;
        // Number of elements, read instantaneously by lock or visibility
        transient int count;
        // Number of modifications
        transient int modCount;
		// When the table size exceeds the threshold, the hash operation is performed again. Its value = (int)(capacity*loadFactor)
        transient int threshold;
		// Load factor
        final float loadFactor;
        final V put(K key, int hash, V value, boolean onlyIfAbsent) {
            HashEntry<K,V> node = tryLock() ? null :
                scanAndLockForPut(key, hash, value);
            V oldValue;
            try {
                HashEntry<K,V>[] tab = table;
                int index = (tab.length - 1) & hash;
                HashEntry<K,V> first = entryAt(tab, index);
                for (HashEntry<K,V> 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<K,V>(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;
        }

        /**
         * Remove; match on key only if value null, else match both.
         */
        final V remove(Object key, int hash, Object value) {
            if (!tryLock())
                scanAndLock(key, hash);
            V oldValue = null;
            try {
                HashEntry<K,V>[] tab = table;
                int index = (tab.length - 1) & hash;
                HashEntry<K,V> e = entryAt(tab, index);
                HashEntry<K,V> pred = null;
                while (e != null) {
                    K k;
                    HashEntry<K,V> next = e.next;
                    if ((k = e.key) == key ||
                        (e.hash == hash && key.equals(k))) {
                        V v = e.value;
                        if (value == null || value == v || value.equals(v)) {
                            if (pred == null)
                                setEntryAt(tab, index, next);
                            else
                                pred.setNext(next);
                            ++modCount;
                            --count;
                            oldValue = v;
                        }
                        break;
                    }
                    pred = e;
                    e = next;
                }
            } finally {
                unlock();
            }
            return oldValue;
        }

        final void clear() {
            lock();
            try {
                HashEntry<K,V>[] tab = table;
                for (int i = 0; i < tab.length ; i++)
                // Unsafe operation    
				setEntryAt(tab, i, null);
                ++modCount;
                count = 0;
            } finally {
                unlock();
            }
        }
    }

  

3, Java8

In Java 8, ConcurrentHashMap has changed a lot compared with the previous version.

Instead of Segment array, Node array is used to store data. The display lock is no longer used in the Node array, but the optimistic lock mechanism of Unsafe.

Segment is reserved for processing read and write of object stream only.

Posted by murdocsvan on Sat, 02 May 2020 06:44:30 -0700