ConcurrentHashMap是线程安全并且高效的HashMap。
为什么要使用ConcurrentHashMap?
线程不安全的HashMap,因为多线程环境下,使用它进行put操作会引起死循环,导致CPU利用率接近100%。所以在并发情况下不能使用HashMap。
效率低下的HashTable容器使用synchronized来保证线程安全,但在线程竞争激烈的情况下HashTable的效率非常低下。因为当一个线程访问HashTable的同步方法时,其他线程访问HashTable的同步方法时,可能会进入阻塞或轮询状态。如线程1使用put进行添加元素,线程2不但不能使用put方法添加元素,并且也不能使用get方法来获取元素,所以竞争越激烈效率越低。
ConcurrentHashMap的锁分段技术
HashTable容器在竞争激烈的并发环境下表现出效率低下的原因是所有访问HashTable的线程都必须竞争通一把锁,那加入容器里有多把锁,每一把锁用于锁容器其中一部分数据,那么当多线程访问容器里不同数据段的数据时,线程间就不会存在锁竞争,从而可以有效的提高并发访问效率。这就是ConcurrentHashMap所使用的锁分段技术,首先将数据分成一段一段的存储,然后给每一段数据配一把锁,当一个线程占用锁访问其中一个段数据的时候,其他段的数据也能被其他线程访问。
ConcurrentHashMap的结构
下面是concurrentHashMap的类图:
ConcurrentHashMap是由Segment数组结构和HashEntry数组结构组成。Segment是一种可重入锁ReentrantLock,在ConcurrentHashMap里扮演锁的角色,HashEntry则用于存储键值对数据。一个ConcurrentHashMap里包含一个Segment数组,Segment的结构和HashMap类似,是一种数组和链表结构,一个Segment守护着一个HashEntry数组里的元素,当对HashEntry数组的数据进行修改时,必须首先获得它对应的Segment锁,如下图:
ConcurrentHashMap的基本策略是通过断将表进行细分,每一个段是一个可并发读的hash table。为了减少足迹,除了一个段其他段都只在第一次需要的时候被构建。为了维护懒构建的可见性,访问段以及段中的元素时必须使用volatile的访问方式,即通过下面说到的segmentAt方法中的Unsafe来完成。此外表元素和entry的 “next”域的volatile-writes在锁操作中使用代价小的“lazySet”写形式。因为这些写总是伴随着锁释放,来维持连续的表更新。
旧版本注意:这个类之前的版本太依赖“final”域,以一个大量的初始化操作花费来避免volatile读。这个设计的一些残留(包括强制segment0的构建)退出来确保序列化的兼容性。
ConcurrentHashMap的代码分析
public class ConcurrentHashMap<K, V> extends AbstractMap<K, V> implements ConcurrentMap<K, V>, Serializable如上图所示,ConcurrentHashMap实现了ConcurrentMap接口,并且是可序列化的。
ConcurrentMap接口如下:
public interface ConcurrentMap<K, V> extends Map<K, V> { /** * If the specified key is not already associated * with a value, associate it with the given value. * This is equivalent to * <pre> * if (!map.containsKey(key)) * return map.put(key, value); * else * return map.get(key);</pre> * except that the action is performed atomically. * * @param key key with which the specified value is to be associated * @param value value to be associated with the specified key * @return the previous value associated with the specified key, or * <tt>null</tt> if there was no mapping for the key. * (A <tt>null</tt> return can also indicate that the map * previously associated <tt>null</tt> with the key, * if the implementation supports null values.) * @throws UnsupportedOperationException if the <tt>put</tt> operation * is not supported by this map * @throws ClassCastException if the class of the specified key or value * prevents it from being stored in this map * @throws NullPointerException if the specified key or value is null, * and this map does not permit null keys or values * @throws IllegalArgumentException if some property of the specified key * or value prevents it from being stored in this map * */ V putIfAbsent(K key, V value); /** * Removes the entry for a key only if currently mapped to a given value. * This is equivalent to * <pre> * if (map.containsKey(key) && map.get(key).equals(value)) { * map.remove(key); * return true; * } else return false;</pre> * except that the action is performed atomically. * * @param key key with which the specified value is associated * @param value value expected to be associated with the specified key * @return <tt>true</tt> if the value was removed * @throws UnsupportedOperationException if the <tt>remove</tt> operation * is not supported by this map * @throws ClassCastException if the key or value is of an inappropriate * type for this map * (<a href="../Collection.html#optional-restrictions">optional</a>) * @throws NullPointerException if the specified key or value is null, * and this map does not permit null keys or values * (<a href="../Collection.html#optional-restrictions">optional</a>) */ boolean remove(Object key, Object value); /** * Replaces the entry for a key only if currently mapped to a given value. * This is equivalent to * <pre> * if (map.containsKey(key) && map.get(key).equals(oldValue)) { * map.put(key, newValue); * return true; * } else return false;</pre> * except that the action is performed atomically. * * @param key key with which the specified value is associated * @param oldValue value expected to be associated with the specified key * @param newValue value to be associated with the specified key * @return <tt>true</tt> if the value was replaced * @throws UnsupportedOperationException if the <tt>put</tt> operation * is not supported by this map * @throws ClassCastException if the class of a specified key or value * prevents it from being stored in this map * @throws NullPointerException if a specified key or value is null, * and this map does not permit null keys or values * @throws IllegalArgumentException if some property of a specified key * or value prevents it from being stored in this map */ boolean replace(K key, V oldValue, V newValue); /** * Replaces the entry for a key only if currently mapped to some value. * This is equivalent to * <pre> * if (map.containsKey(key)) { * return map.put(key, value); * } else return null;</pre> * except that the action is performed atomically. * * @param key key with which the specified value is associated * @param value value to be associated with the specified key * @return the previous value associated with the specified key, or * <tt>null</tt> if there was no mapping for the key. * (A <tt>null</tt> return can also indicate that the map * previously associated <tt>null</tt> with the key, * if the implementation supports null values.) * @throws UnsupportedOperationException if the <tt>put</tt> operation * is not supported by this map * @throws ClassCastException if the class of the specified key or value * prevents it from being stored in this map * @throws NullPointerException if the specified key or value is null, * and this map does not permit null keys or values * @throws IllegalArgumentException if some property of the specified key * or value prevents it from being stored in this map */ V replace(K key, V value); }从上面代码可知,ConcurrentMap接口扩展了Map接口,同时也增加了适合于并发的几个特殊的方法,具体功能参考注释即可明白。
/** * The default initial capacity for this table, * used when not otherwise specified in a constructor. */ static final int DEFAULT_INITIAL_CAPACITY = 16;默认的加载因子
/** * The default load factor for this table, used when not * otherwise specified in a constructor. */ static final float DEFAULT_LOAD_FACTOR = 0.75f;默认的并发级别
/** * The default concurrency level for this table, used when not * otherwise specified in a constructor. */ static final int DEFAULT_CONCURRENCY_LEVEL = 16;最大容量
/** * The maximum capacity, used if a higher value is implicitly * specified by either of the constructors with arguments. MUST * be a power of two <= 1<<30 to ensure that entries are indexable * using ints. */ static final int MAXIMUM_CAPACITY = 1 << 30;段的最小容量
/** * The minimum capacity for per-segment tables. Must be a power * of two, at least two to avoid immediate resizing on next use * after lazy construction. */ static final int MIN_SEGMENT_TABLE_CAPACITY = 2;最大段数
/** * The maximum number of segments to allow; used to bound * constructor arguments. Must be power of two less than 1 << 24. */ static final int MAX_SEGMENTS = 1 << 16; // slightly conservative在得到锁之前重试的次数(size和containsValue方法)。
/** * Number of unsynchronized retries in size and containsValue * methods before resorting to locking. This is used to avoid * unbounded retries if tables undergo continuous modification * which would make it impossible to obtain an accurate result. */ static final int RETRIES_BEFORE_LOCK = 2;下面是属性的其他一些域:
private static class Holder { /** * *不像其他的map实现,我们不实现阈值调整是否用于散列键值, *代替散列或者对全部实例生效,或者对全部实例不生效 */ /** * */ static final boolean ALTERNATIVE_HASHING; static { // Use the "threshold" system property even though our threshold // behaviour is "ON" or "OFF". String altThreshold = java.security.AccessController.doPrivileged( new sun.security.action.GetPropertyAction( "jdk.map.althashing.threshold")); int threshold; try { threshold = (null != altThreshold) ? Integer.parseInt(altThreshold) : Integer.MAX_VALUE; // disable alternative hashing if -1 if (threshold == -1) { threshold = Integer.MAX_VALUE; } if (threshold < 0) { throw new IllegalArgumentException("value must be positive integer."); } } catch(IllegalArgumentException failed) { throw new Error("Illegal value for 'jdk.map.althashing.threshold'", failed); } ALTERNATIVE_HASHING = threshold <= MAXIMUM_CAPACITY; } }生成hash种子。
/** * 一个与这个实例相关的随机值应用于键值的hash码,来减少hash冲突 */ private transient final int hashSeed = randomHashSeed(this); private static int randomHashSeed(ConcurrentHashMap instance) { if (sun.misc.VM.isBooted() && Holder.ALTERNATIVE_HASHING) { return sun.misc.Hashing.randomHashSeed(instance); } return 0; }段的掩码(掩码可参考计算机网络)
/** * 进入段的索引掩码值,键值的hash码的高位用来选择段 */ final int segmentMask;段中偏移量
/** * 段中索引的偏移量 */ final int segmentShift;所有的段
/** * 所有的段,每个段是一个hash table */ final Segment<K,V>[] segments;主键集合,entry集合,值集合
transient Set<K> keySet; transient Set<Map.Entry<K,V>> entrySet; transient Collection<V> values;实际存储值的HashEntry静态类:
/** * ConcurrentHashMapentry列表,注意,这个静态类永远不会作为一个用户可见的Map.Entry被导出。 */ static final class HashEntry<K,V> { final int hash; final K key; volatile V value; volatile HashEntry<K,V> next; HashEntry(int hash, K key, V value, HashEntry<K,V> next) { this.hash = hash; this.key = key; this.value = value; this.next = next; } /** * Sets next field with volatile write semantics. (See above * about use of putOrderedObject.) */ final void setNext(HashEntry<K,V> n) { UNSAFE.putOrderedObject(this, nextOffset, n); } // Unsafe mechanics static final sun.misc.Unsafe UNSAFE; static final long nextOffset; static { try { UNSAFE = sun.misc.Unsafe.getUnsafe(); Class k = HashEntry.class; nextOffset = UNSAFE.objectFieldOffset (k.getDeclaredField("next")); } catch (Exception e) { throw new Error(e); } } }获取第i个HashEntry
/** *使用volatile的读取语义获得给定table的第i个元素(如果table不为空),注意 *这是被手动的融入到一个低性能的方法类减少方法调用开销 */ @SuppressWarnings("unchecked") static final <K,V> HashEntry<K,V> entryAt(HashEntry<K,V>[] tab, int i) { return (tab == null) ? null : (HashEntry<K,V>) UNSAFE.getObjectVolatile (tab, ((long)i << TSHIFT) + TBASE); }
/** *设置给定table的第i个元素,使用volatile的写语义(可以看下面关于putOrderedObject使用)。 */ static final <K,V> void setEntryAt(HashEntry<K,V>[] tab, int i, HashEntry<K,V> e) { UNSAFE.putOrderedObject(tab, ((long)i << TSHIFT) + TBASE, e); }补充的hash函数
/** *对hashCode使用一个补充的hash函数,这能够克服hash函数的低质量。 *这是非常重要的,因为ConcurrentHashMap使用2的幂次长度的hash tables, *否则就会遭遇hashCodes在低位或者高位的冲突。 */ private int hash(Object k) { int h = hashSeed; if ((0 != h) && (k instanceof String)) { return sun.misc.Hashing.stringHash32((String) k); } h ^= k.hashCode(); // Spread bits to regularize both segment and index locations, // using variant of single-word Wang/Jenkins hash. h += (h << 15) ^ 0xffffcd7d; h ^= (h >>> 10); h += (h << 3); h ^= (h >>> 6); h += (h << 2) + (h << 14); return h ^ (h >>> 16); }静态常量内部类Segment,Segment代表着table里的段,并且定义了相关的操作方法。
/** *哈希表的专门版本-段。是ReentrantLock的子类,只求简化一些 *锁来避免分离的构建 */ static final class Segment<K,V> extends ReentrantLock implements Serializable { /* *段维护了一个entry列表的table。总是能够保持一致性状态,因此能够被无需锁来 *读(通过段和table的volatile的读)。当table改变大小需要时需要复制节点,因此, *旧的列表能够被读取者仍然使用table的旧版本访问。 *这个类仅仅定义了需要锁的变化方法。除非另有注明,这个类的方法执行 *ConcurrentHashMap方法每个段的版本。(其他方法被直接融入到ConcurrentHashMap *方法中。)这些可变方法使用一种自旋控制竞争通过scanAndLocs和scanAndLockForPut *方法。他们使用对节点的遍历来尝试获取锁。这个的主要益处是吸收了高速缓存确实 *(在hash tables中是非常常见的)当获取锁以至于一旦获得访问更快。我们其实不是用 *找到的节点,因为必须被在锁中再次获得,无论怎样来确保连续的一致性更新(在任何 *情况下探测到的都是新的),但是正常的他们将被更快的进行再定位。scanAndLockForPut *探索性的创建一个新的节点用于put,如果没有节点被找到。 */ private static final long serialVersionUID = 2249069246763182397L; /** *在准备对一个段进行锁操作产生可能的阻塞之前尝试获取锁的最大次数。 *在多处理器上,使用一个有限数量的充实来维护当定位节点时需要的缓存。 */ static final int MAX_SCAN_RETRIES = Runtime.getRuntime().availableProcessors() > 1 ? 64 : 1; /** *每一个段的table。元素通过entryAt/setEntryAt提供的volatile语义来访问。 */ transient volatile HashEntry<K,V>[] table; /** *元素的数量。或者使用锁,或者在其他volatile读来维护可见性访问。 */ transient int count; /** *在这个段中修改操作的总数量。即使这可能超过32位,它为稳定性检查提供了足够的 *准确性,可参考isEmpty()和size()方法的参考文档。只能够或者加锁或者其他volatile *读的方式来维护可见性。 */ transient int modCount; /** *table在它的大小超过了阈值会进行再哈希。(这个域的值总是int(capacity*loadFactor)) */ transient int threshold; /** *为hash table提供的加载因子。即使这个值对于所有的段是相同的, *但是它也要被复制来避免链接到外部对象。 */ final float loadFactor; Segment(float lf, int threshold, HashEntry<K,V>[] tab) { this.loadFactor = lf; this.threshold = threshold; this.table = tab; } 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; } /** *使table的大小加倍并且重组entries,同时把给定的node加到新的table */ @SuppressWarnings("unchecked") private void rehash(HashEntry<K,V> node) { /* *重新把每个列表里节点分类到新的table中。因为我们使用2的幂次扩展, *每个bin里的元素或者有相同的索引,或者移动2的幂次的偏移。我们消除 *不必要的节点创建通过捕获旧节点能被再使用的情况因为他们的下一个域将 *不改变。据统计,在默认的阈值下,当一个table复制的时候,只有1/6的 *元素需要克隆。一旦他们没有被正在并并发遍历的表中的读线程引用,他们 *替换的节点将变为可垃圾回收的。Entry访问使用普通的数组索引,因为他们 *紧跟着进行volatile table写。 */ HashEntry<K,V>[] oldTable = table; int oldCapacity = oldTable.length; int newCapacity = oldCapacity << 1; 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) // Single node on list newTable[idx] = e; else { // Reuse consecutive sequence at same slot HashEntry<K,V> lastRun = e; int lastIdx = idx; for (HashEntry<K,V> last = next; last != null; last = last.next) { int k = last.hash & sizeMask; if (k != lastIdx) { lastIdx = k; lastRun = last; } } newTable[lastIdx] = lastRun; // Clone remaining nodes 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]); newTable[nodeIndex] = node; table = newTable; } /** *扫描一个包含给定key的节点,当试图得到锁,如果没有找到则创建并返回一个 *根据返回值来确定锁被拥有了。和大多数方法不同,调用方法equals是不被屏蔽 *的:因为遍历速度无关紧要,我们可以热点化相关代码和访问。 * Scans for a node containing given key while trying to * acquire lock, creating and returning one if not found. Upon * return, guarantees that lock is held. UNlike in most * methods, calls to method equals are not screened: Since * traversal speed doesn't matter, we might as well help warm * up the associated code and accesses as well. * * @return a new node if key not found, else null */ private HashEntry<K,V> scanAndLockForPut(K key, int hash, V value) { HashEntry<K,V> first = entryForHash(this, hash); HashEntry<K,V> e = first; HashEntry<K,V> node = null; int retries = -1; // negative while locating node while (!tryLock()) { HashEntry<K,V> f; // to recheck first below if (retries < 0) { if (e == null) { if (node == null) // speculatively create node node = new HashEntry<K,V>(hash, key, value, null); retries = 0; } else if (key.equals(e.key)) retries = 0; else e = e.next; } else if (++retries > MAX_SCAN_RETRIES) { lock(); break; } else if ((retries & 1) == 0 && (f = entryForHash(this, hash)) != first) { e = first = f; // re-traverse if entry changed retries = -1; } } return node; } /** *在尝试获得锁来进行移除和替换操作的时候。扫描一个包含给定key的节点 *根据返回值来确保持有锁。注意我们必须锁住即使key没有找到,来确保一 *系列的更新操作 */ private void scanAndLock(Object key, int hash) { // similar to but simpler than scanAndLockForPut HashEntry<K,V> first = entryForHash(this, hash); HashEntry<K,V> e = first; int retries = -1; while (!tryLock()) { HashEntry<K,V> f; if (retries < 0) { if (e == null || key.equals(e.key)) retries = 0; else e = e.next; } else if (++retries > MAX_SCAN_RETRIES) { lock(); break; } else if ((retries & 1) == 0 && (f = entryForHash(this, hash)) != first) { e = first = f; retries = -1; } } } /** *移除,如果值为空只匹配key,否则匹配所有。 */ 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 boolean replace(K key, int hash, V oldValue, V newValue) { if (!tryLock()) scanAndLock(key, hash); boolean replaced = false; try { HashEntry<K,V> e; for (e = entryForHash(this, hash); e != null; e = e.next) { K k; if ((k = e.key) == key || (e.hash == hash && key.equals(k))) { if (oldValue.equals(e.value)) { e.value = newValue; ++modCount; replaced = true; } break; } } } finally { unlock(); } return replaced; } final V replace(K key, int hash, V value) { if (!tryLock()) scanAndLock(key, hash); V oldValue = null; try { HashEntry<K,V> e; for (e = entryForHash(this, hash); e != null; e = e.next) { K k; if ((k = e.key) == key || (e.hash == hash && key.equals(k))) { oldValue = e.value; e.value = value; ++modCount; break; } } } finally { unlock(); } return oldValue; } final void clear() { lock(); try { HashEntry<K,V>[] tab = table; for (int i = 0; i < tab.length ; i++) setEntryAt(tab, i, null); ++modCount; count = 0; } finally { unlock(); } } }下面是访问Segment的方法:
/** *使用通过Unsafe实现的volatile元素访问获取权限给定段数组的第j个 *元素(如果不空)。空检查会在反序列化期间无害的触发。注意:因为 *每一个段数组的元素只被设置一次(使用全局顺序的写),一些性能敏感 *的方法依赖于这个方法作为一种基于无效读的重新检查。 */ @SuppressWarnings("unchecked") static final <K,V> Segment<K,V> segmentAt(Segment<K,V>[] ss, int j) { long u = (j << SSHIFT) + SBASE; return ss == null ? null : (Segment<K,V>) UNSAFE.getObjectVolatile(ss, u); }对于给定的key,如果存在段则返回,如果不存在,通过CAS创建并记录,确保有segment
/** *对于给定的索引返回段,如果还没有存在,在段表中创建并记录它(通过 CAS) * @param k the index * @return the segment */ @SuppressWarnings("unchecked") 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) { 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)) == null) { if (UNSAFE.compareAndSwapObject(ss, u, null, seg = s)) break; } } } return seg; }下面是基于hash的segment和entry访问。
/** * 给定hash得到segment */ @SuppressWarnings("unchecked") private Segment<K,V> segmentForHash(int h) { long u = (((h >>> segmentShift) & segmentMask) << SSHIFT) + SBASE; return (Segment<K,V>) UNSAFE.getObjectVolatile(segments, u); }
/** *对给定的segment和hash值得到table entry */ @SuppressWarnings("unchecked") static final <K,V> HashEntry<K,V> entryForHash(Segment<K,V> seg, int h) { HashEntry<K,V>[] tab; return (seg == null || (tab = seg.table) == null) ? null : (HashEntry<K,V>) UNSAFE.getObjectVolatile (tab, ((long)(((tab.length - 1) & h)) << TSHIFT) + TBASE); }线面是像用户开放的公用操作:
/** *使用给定的初始化容量,加载因子和并发级别创建一个新的、空的map * * @param initialCapacity the initial capacity. The implementation * performs internal sizing to accommodate this many elements. * @param loadFactor the load factor threshold, used to control resizing. * Resizing may be performed when the average number of elements per * bin exceeds this threshold. * @param concurrencyLevel the estimated number of concurrently * updating threads. The implementation performs internal sizing * to try to accommodate this many threads. * @throws IllegalArgumentException if the initial capacity is * negative or the load factor or concurrencyLevel are * nonpositive. */ @SuppressWarnings("unchecked") 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; }另一个构造ConcurrentHashMap的方法,只传入初始化容量和加载因子
/** *使用给定的初始化容量和加载因子,并且使用默认的并发级别(16)创建 *一个新的、空的map * * @param initialCapacity The implementation performs internal * sizing to accommodate this many elements. * @param loadFactor the load factor threshold, used to control resizing. * Resizing may be performed when the average number of elements per * bin exceeds this threshold. * @throws IllegalArgumentException if the initial capacity of * elements is negative or the load factor is nonpositive * * @since 1.6 */ public ConcurrentHashMap(int initialCapacity, float loadFactor) { this(initialCapacity, loadFactor, DEFAULT_CONCURRENCY_LEVEL); }只有一个初始化容量的参数构造:
/** *使用给定的初始化容量和默认的加载因子(0.75)和默认的并发级别(16) *来创建和新的和空的map * * @param initialCapacity the initial capacity. The implementation * performs internal sizing to accommodate this many elements. * @throws IllegalArgumentException if the initial capacity of * elements is negative. */ public ConcurrentHashMap(int initialCapacity) { this(initialCapacity, DEFAULT_LOAD_FACTOR, DEFAULT_CONCURRENCY_LEVEL); }使用默认构造方法创建:
/** *使用默认的初始化容量(16)、加载因子(0.75)、并发级别(16)来创建一个 *新的空的map */ public ConcurrentHashMap() { this(DEFAULT_INITIAL_CAPACITY, DEFAULT_LOAD_FACTOR, DEFAULT_CONCURRENCY_LEVEL); }使用另一个map作为构造参数来创建:
/** *使用相同的映射作为给定的map来创建出一个新的map,这个map使用 *给定map的映射数目的1.5倍容量或者16(可能更高),默认的加载因子(0.75) *,默认的并发级别(16)来创建 * * @param m the map */ public ConcurrentHashMap(Map<? extends K, ? extends V> m) { this(Math.max((int) (m.size() / DEFAULT_LOAD_FACTOR) + 1, DEFAULT_INITIAL_CAPACITY), DEFAULT_LOAD_FACTOR, DEFAULT_CONCURRENCY_LEVEL); putAll(m); }由上面的构造方法可知,ConcurrentHashMap初始化方法是通过initialCapacity,loadFactor,从currencyLevel几个参数来初始化segments数组,段偏移量segmentShift,段掩码segmentMask和每个segment里的HashEntry数组。
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),上面代码中的变量cap就是segment里的HashEntry数组的长度,它等于initialCapacity除以ssize的倍数,如果大于1,就回去大于等于c的2的N次方值,所以cap不是1,就是2的N次方。segment的容量threshold=(int)cap*loadFactor,默认情况下initialCapacity等于16,loadfactor等于0.75,通过运算cap等于1,threshold等于零。
private int hash(Object k) { int h = hashSeed; if ((0 != h) && (k instanceof String)) { return sun.misc.Hashing.stringHash32((String) k); } h ^= k.hashCode(); // Spread bits to regularize both segment and index locations, // using variant of single-word Wang/Jenkins hash. h += (h << 15) ^ 0xffffcd7d; h ^= (h >>> 10); h += (h << 3); h ^= (h >>> 6); h += (h << 2) + (h << 14); return h ^ (h >>> 16); }之所以进行再哈希,其目的是为了减少哈希冲突,使元素能够均匀的分布在不同的Segment上,从而提高容器的存取效率。加入哈希的质量差到极点,那么所有的元素都在一个Segment中,不仅存取元素缓慢,分段也会失去意义。通过这种再哈希能让数字的每一位都能参加到哈希运算当中,从而减少哈希冲突。以下是ConcurrentHashMap定位segment的方法。
private Segment<K,V> segmentForHash(int h) { long u = (((h >>> segmentShift) & segmentMask) << SSHIFT) + SBASE; return (Segment<K,V>) UNSAFE.getObjectVolatile(segments, u); }
/** *如果map没有包含key-value映射返回true * * @return <tt>true</tt> if this map contains no key-value mappings */ public boolean isEmpty() { /* *总计每一个段的修改次数来避免当检查另一个segment时在一个 *segment里并发的增加和移除,在这种情况下,table实际上在任 *何时间将都不为空。(这个统计确保了在复查之前至少每个段1<<31 *的修改)size()和containsValue()方法使用类似的方式来确保稳定 *的检查 */ long sum = 0L; final Segment<K,V>[] segments = this.segments; for (int j = 0; j < segments.length; ++j) { Segment<K,V> seg = segmentAt(segments, j); if (seg != null) { if (seg.count != 0) return false; sum += seg.modCount; } } if (sum != 0L) { // recheck unless no modifications for (int j = 0; j < segments.length; ++j) { Segment<K,V> seg = segmentAt(segments, j); if (seg != null) { if (seg.count != 0) return false; sum -= seg.modCount; } } if (sum != 0L) return false; } return true; }获取map的size():
/** *返回在这个map中的key-value映射的数量。如果map包含了超过 *Integer.MAX_VALUE *的元素,放回Integer.MAX_VALUE * * @return the number of key-value mappings in this map */ public int size() { // Try a few times to get accurate count. On failure due to // continuous async changes in table, resort to locking. final Segment<K,V>[] segments = this.segments; int size; boolean overflow; // true if size overflows 32 bits long sum; // sum of modCounts long last = 0L; // previous sum int retries = -1; // first iteration isn't retry try { for (;;) { if (retries++ == RETRIES_BEFORE_LOCK) { for (int j = 0; j < segments.length; ++j) ensureSegment(j).lock(); // force creation } sum = 0L; size = 0; overflow = false; for (int j = 0; j < segments.length; ++j) { Segment<K,V> seg = segmentAt(segments, j); if (seg != null) { sum += seg.modCount; int c = seg.count; if (c < 0 || (size += c) < 0) overflow = true; } } if (sum == last) break; last = sum; } } finally { if (retries > RETRIES_BEFORE_LOCK) { for (int j = 0; j < segments.length; ++j) segmentAt(segments, j).unlock(); } } return overflow ? Integer.MAX_VALUE : size; }根据key值返回value值或者null
/** *返回与给定的key匹配的value或者放回null,如果map没有包含这 *个key的映射 *更正式的讲,如果一个map包含了一个key到value的映射,比如: *key.equals(k),这个方法返回v,否则它将返回null(也可能至多 *有这样一个映射)。 * * @throws NullPointerException if the specified key is null */ 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 && (tab = s.table) != null) { for (HashEntry<K,V> e = (HashEntry<K,V>) 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; }
/** * 测试给定的key是否包含在map中 * * @param key possible key * @return <tt>true</tt> if and only if the specified object * is a key in this table, as determined by the * <tt>equals</tt> method; <tt>false</tt> otherwise. * @throws NullPointerException if the specified key is null */ @SuppressWarnings("unchecked") public boolean containsKey(Object key) { Segment<K,V> s; // same as get() except no need for volatile value read 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 && (tab = s.table) != null) { for (HashEntry<K,V> e = (HashEntry<K,V>) 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 true; } } return false; }判断map是否包含某个value值:
/** *如果一个map中,给定的value匹配一个或者多个key则返回true。 *注意:这个方法需要对整个hash table进行遍历,所以是远远 *慢于containsKey方法。 * * @param value value whose presence in this map is to be tested * @return <tt>true</tt> if this map maps one or more keys to the * specified value * @throws NullPointerException if the specified value is null */ public boolean containsValue(Object value) { // Same idea as size() if (value == null) throw new NullPointerException(); final Segment<K,V>[] segments = this.segments; boolean found = false; long last = 0; int retries = -1; try { outer: for (;;) { if (retries++ == RETRIES_BEFORE_LOCK) { for (int j = 0; j < segments.length; ++j) ensureSegment(j).lock(); // force creation } long hashSum = 0L; int sum = 0; for (int j = 0; j < segments.length; ++j) { HashEntry<K,V>[] tab; Segment<K,V> seg = segmentAt(segments, j); if (seg != null && (tab = seg.table) != null) { for (int i = 0 ; i < tab.length; i++) { HashEntry<K,V> e; for (e = entryAt(tab, i); e != null; e = e.next) { V v = e.value; if (v != null && value.equals(v)) { found = true; break outer; } } } sum += seg.modCount; } } if (retries > 0 && sum == last) break; last = sum; } } finally { if (retries > RETRIES_BEFORE_LOCK) { for (int j = 0; j < segments.length; ++j) segmentAt(segments, j).unlock(); } } return found; }为了兼容hashTable遗留的方法
/** *遗留的方法类测试在table里是否有一些key与给定的value值匹配 *这个方法与containsValue方法是完全一致的,它的存在完全是为了 *确保与类java.util.HashTable兼容。支持这个方法由于Java Conllections *framework的介绍。 * @param value a value to search for * @return <tt>true</tt> if and only if some key maps to the * <tt>value</tt> argument in this table as * determined by the <tt>equals</tt> method; * <tt>false</tt> otherwise * @throws NullPointerException if the specified value is null */ public boolean contains(Object value) { return containsValue(value); }将映射加入map
/** *在table中创建给定key到给定value的映射,key和value都不能为空 *可以通过调用get方法使用一个等于初始key的key来回取value * * @param key key with which the specified value is to be associated * @param value value to be associated with the specified key * @return the previous value associated with <tt>key</tt>, or * <tt>null</tt> if there was no mapping for <tt>key</tt> * @throws NullPointerException if the specified key or value is null */ @SuppressWarnings("unchecked") public V put(K key, V value) { Segment<K,V> s; if (value == null) throw new NullPointerException(); int hash = hash(key); 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); }如果不存在,在加入map:
/** * {@inheritDoc} * * @return the previous value associated with the specified key, * or <tt>null</tt> if there was no mapping for the key * @throws NullPointerException if the specified key or value is null */ @SuppressWarnings("unchecked") public V putIfAbsent(K key, V value) { Segment<K,V> s; if (value == null) throw new NullPointerException(); int hash = hash(key); int j = (hash >>> segmentShift) & segmentMask; if ((s = (Segment<K,V>)UNSAFE.getObject (segments, (j << SSHIFT) + SBASE)) == null) s = ensureSegment(j); return s.put(key, hash, value, true); }
/** *将映射从一个给定的map拷贝到这个。这些映射将替换原map中存在的映射 * * @param m mappings to be stored in this map */ public void putAll(Map<? extends K, ? extends V> m) { for (Map.Entry<? extends K, ? extends V> e : m.entrySet()) put(e.getKey(), e.getValue()); }移除元素
/** *从map中移除key(以及和他相关的value),如果在map中不存在这个key, *将不做任何事情。 * Removes the key (and its corresponding value) from this map. * This method does nothing if the key is not in the map. * * @param key the key that needs to be removed * @return the previous value associated with <tt>key</tt>, or * <tt>null</tt> if there was no mapping for <tt>key</tt> * @throws NullPointerException if the specified key is null */ public V remove(Object key) { int hash = hash(key); Segment<K,V> s = segmentForHash(hash); return s == null ? null : s.remove(key, hash, null); }根据给定的key和value移除元素
/** * {@inheritDoc} * * @throws NullPointerException if the specified key is null */ public boolean remove(Object key, Object value) { int hash = hash(key); Segment<K,V> s; return value != null && (s = segmentForHash(hash)) != null && s.remove(key, hash, value) != null; }根据key更新value
/** * {@inheritDoc} * * @throws NullPointerException if any of the arguments are null */ public boolean replace(K key, V oldValue, V newValue) { int hash = hash(key); if (oldValue == null || newValue == null) throw new NullPointerException(); Segment<K,V> s = segmentForHash(hash); return s != null && s.replace(key, hash, oldValue, newValue); }
/** * {@inheritDoc} * * @return the previous value associated with the specified key, * or <tt>null</tt> if there was no mapping for the key * @throws NullPointerException if the specified key or value is null */ public V replace(K key, V value) { int hash = hash(key); if (value == null) throw new NullPointerException(); Segment<K,V> s = segmentForHash(hash); return s == null ? null : s.replace(key, hash, value); }清空map
/** * Removes all of the mappings from this map. */ public void clear() { final Segment<K,V>[] segments = this.segments; for (int j = 0; j < segments.length; ++j) { Segment<K,V> s = segmentAt(segments, j); if (s != null) s.clear(); } }key的集合
/** * Returns a {@link Set} view of the keys contained in this map. * The set is backed by the map, so changes to the map are * reflected in the set, and vice-versa. The set supports element * removal, which removes the corresponding mapping from this map, * via the <tt>Iterator.remove</tt>, <tt>Set.remove</tt>, * <tt>removeAll</tt>, <tt>retainAll</tt>, and <tt>clear</tt> * operations. It does not support the <tt>add</tt> or * <tt>addAll</tt> operations. * * <p>The view's <tt>iterator</tt> is a "weakly consistent" iterator * that will never throw {@link ConcurrentModificationException}, * and guarantees to traverse elements as they existed upon * construction of the iterator, and may (but is not guaranteed to) * reflect any modifications subsequent to construction. */ public Set<K> keySet() { Set<K> ks = keySet; return (ks != null) ? ks : (keySet = new KeySet()); }返回值的集合
/** * Returns a {@link Collection} view of the values contained in this map. * The collection is backed by the map, so changes to the map are * reflected in the collection, and vice-versa. The collection * supports element removal, which removes the corresponding * mapping from this map, via the <tt>Iterator.remove</tt>, * <tt>Collection.remove</tt>, <tt>removeAll</tt>, * <tt>retainAll</tt>, and <tt>clear</tt> operations. It does not * support the <tt>add</tt> or <tt>addAll</tt> operations. * * <p>The view's <tt>iterator</tt> is a "weakly consistent" iterator * that will never throw {@link ConcurrentModificationException}, * and guarantees to traverse elements as they existed upon * construction of the iterator, and may (but is not guaranteed to) * reflect any modifications subsequent to construction. */ public Collection<V> values() { Collection<V> vs = values; return (vs != null) ? vs : (values = new Values()); }需要强调的几点:
相关推荐
【Java并发容器之ConcurrentHashMap】是Java编程中用于高效并发操作的重要工具。相比于HashMap,ConcurrentHashMap在多线程环境下提供了线程安全的保证,避免了因扩容导致的CPU资源消耗过高问题。传统的线程安全解决...
【Java并发集合之ConcurrentHashMap详解】 在Java的并发编程中,`ConcurrentHashMap`是一个极其重要的工具,它是线程安全的哈希映射表,提供了高效且安全的并发访问性能。相较于传统的线程安全的`Hashtable`和非...
- **`Segment`**:它是`ConcurrentHashMap`的核心组件之一,负责存储数据并管理锁。 - `count`:表示当前`Segment`中的元素个数。 - `modCount`:记录了对`table`进行修改的次数。 - `threshold`:扩容阈值,当...
这些知识点仅仅是Java容器对象的一部分,实际的博客可能会包含更多细节,如源码分析、性能对比和最佳实践。通过阅读博客中的`持有对象.xmind`文件,可以进一步了解博主对这些概念的详细整理和分类。如果你对Java容器...
Java容器是Java编程中至关重要的一个部分,它们用于存储、管理和操作对象集合。在这个主题下,我们将深入探讨Java中的核心容器类,包括数组、List、Set和Map,以及它们各自的特点和使用场景。 1. **数组**:数组是...
接着,我们来看`ConcurrentHashMap`,它是Java提供的线程安全的散列映射容器,适用于高并发环境。在Java 7中,`ConcurrentHashMap`采用了分段锁的设计,将整个哈希表分成若干个段(Segment),每个段有自己的锁,...
Java并发容器ConcurrentHashMap是Java中最常用的数据结构之一,它的出现是为了解决HashMap在多线程并发环境下的线程不安全问题。在ConcurrentHashMap中,put方法是最基本也是最重要的操作之一,它的正确实现对整个...
通过这些练习,你将巩固对Java容器的理解,提高代码编写效率,并为解决实际问题打下坚实基础。记得在实践中不断挑战自己,尝试不同的场景和数据结构,以便更好地掌握Java容器的精髓。祝你在学习过程中取得优异的成绩...
Java容器类的应用不仅限于这些基本类型,还有`Collections.synchronizedXXX`方法创建的同步容器,以及`ConcurrentHashMap`这样的高级并发容器,它们在多线程环境下提供了更好的性能。 总的来说,理解和熟练使用Java...
某些容器如ArrayList和HashMap在并发环境下直接使用可能会导致数据不一致,这时可以考虑使用Collections.synchronizedXXX()方法进行同步,或者使用并发容器如CopyOnWriteArrayList和ConcurrentHashMap。 总之,Java...
- **并发处理**:某些容器(如Vector和Hashtable)提供了内置的线程安全性,但在高性能、高并发的环境下,推荐使用Java并发包中的`ConcurrentHashMap`等并发容器。 - **工厂方法**:Java SDK提供了`Collections`工具...
Java 类容器是 Java 编程中非常重要的一个概念,它主要指的是 Java 集合框架中的各种类,如 ArrayList、LinkedList、HashSet、HashMap 等,这些类用于存储和管理对象。本文将深入探讨这些常用的Java类容器,帮助...
除了`CopyOnWrite`系列容器,`java.util.concurrent`包还包含其他并发容器,如`ConcurrentHashMap`,它使用分段锁技术,允许并发读写,相比于`HashTable`有更高的并发性能。`ConcurrentLinkedQueue`是一个无界的线程...
Java容器是Java编程中至关重要的一个概念,它们是用来存储、管理和操作对象的工具,使得开发者可以更加方便地组织代码和数据。在这个“JAVA的容器自学”资料中,我们将深入探讨Java容器的基本概念、主要类型以及如何...
ConcurrentHashMap 初始容量默认为16段(segment),使用分段锁设计 ConcurrentLinkedQueue 高并发下性能最好的队列 无锁,采用CAS比较算法,核心参数(V,E,N) V:要更新的变量 E:预期值 N:新值 只有当V==E时,V=N;...
Java容器在Java编程中起着至关重要的作用,它们是用来存储和管理对象的工具。这篇文档主要探讨了17个关于Java容器的关键问题,涵盖了不同类型的容器、它们的区别、实现原理以及线程安全等方面。 1. **Java容器都有...
总结,`ConcurrentHashMap`是Java并发编程中重要的并发容器,它的设计兼顾了线程安全和性能,是理解和掌握多线程编程的关键知识点之一。在面试或实际开发中,对于`ConcurrentHashMap`的原理和用法的深入理解,能够...
针对线程安全问题,Java提供了ConcurrentHashMap类,它是线程安全的,并且能够有效避免在多线程环境下HashMap的扩容问题。ConcurrentHashMap通过使用分段锁(Segmentation)的方式来提高并发访问的能力,它将数据...
Unsafe 详细解Java SPI 机制详解Java语法糖详解集合知识点/面试题总结:Java集合常见知识点&面试题总结(上)(必看)Java集合常见知识点&面试题总结(下)(必看)Java容器使用注意事项总结源码分析:ArrayList核心...