ConcurrentHashMap 从Java7 到 Java8的改变
一、关于分段锁
集合框架很大程度减少了java程序员的重复劳动,然而,在Java多线程环境中,以线程安全的方式使用集合类是一个首先考虑的问题。
越来越多的程序员了解到了ConcurrentHashMap,都知道它内部使用了分段锁。然而,随着Java语言的快速发展,分段锁已经是历史了。
在Java8中,你看不到分段锁的身影。Java7中,Segment继承于ReentrantLock使用了显示锁,在Segment的实例方法中,每个更新操作内部又使用Unsafe来处理更新。这显然是一种浪费。显示锁、Unsafe 这二者都是可以保证对对象的原子操作。使用一个即可。
Java7中的数据存储在数组 final Segment<K,V>[] segments; 这个一个特定大小的Segment数组,
Segment继承于ReentrantLock 故而可以在更新操作时使用显示锁。
二、内部类 Segment
static final class Segment<K,V> extends ReentrantLock implements Serializable { private static final long serialVersionUID = 2249069246763182397L; // tryLock()最多等待时间 static final int MAX_SCAN_RETRIES = Runtime.getRuntime().availableProcessors() > 1 ? 64 : 1; // 分段表,元素通过entryAt/setEntryAt这两个方法提供了瞬时操作。 transient volatile HashEntry<K,V>[] table; // 元素数量,通过锁或可见性地瞬时读取 transient int count; // 修改次数 transient int modCount; // 表大小超出threshold时,重新哈希运算。它的值 = (int)(capacity*loadFactor) transient int threshold; // 负载因子 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 操作 setEntryAt(tab, i, null); ++modCount; count = 0; } finally { unlock(); } } }
三、Java8
Java8中,ConcurrentHashMap较之前版本有了很大的改变。
使用Node数组替代了Segment数组来存储数据。Node数组中不再使用显示锁,而是Unsafe的乐观锁机制。
Segment只是予以保留,仅用来处理对象流的读写。