CurrentHashMap详解
一、简单介绍
CurrentHashMap是线程安全的键值对容器,支持并发操作,外围由一个segment数组组成,每个segment是一个锁;每个segment内部是一个数组加链表的结构。
二、CurrentHashMap结构分析
三、源码解读
1、属性
/** * 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 maximum number of segments to allow; used to bound * constructor arguments. */ static final int MAX_SEGMENTS = 1 << 16; // slightly conservative /** * 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; /* ---------------- Fields -------------- */ /** * Mask value for indexing into segments. The upper bits of a * key's hash code are used to choose the segment. */ final int segmentMask; /** * Shift value for indexing within segments. */ final int segmentShift; /** * The segments, each of which is a specialized hash table */ final Segment<K,V>[] segments; transient Set<K> keySet; transient Set<Map.Entry<K,V>> entrySet; transient Collection<V> values;
属性名 | 修饰符 | 值 | 用途 |
DEFAULT_INITIAL_CAPACITY | static final int | 16 | Map默认容量,capacity指所有segement数组大小之和 |
DEFAULT_LOAD_FACTOR | static final float | 0.75f | Map默认加载因子 |
DEFAULT_CONCURRENCY_LEVEL | static final int | 16 | 默认并发数,即segement数组大小 |
MAXIMUM_CAPACITY | static final int | 1 << 30 | 最大容量限制 |
MAX_SEGMENTS | static final int | 1 << 16 | 最大segment |
RETRIES_BEFORE_LOCK | static final int | 2 | ? |
segmentMask | final int | segement数组大小-1 | 定位到segment下标的掩码,其实是对hash值的“&”运算,因为segement大小为2的幂,“&”相当于取膜。 |
segmentShift | final int | 32-segement幂值 | 定位到segment下标时,用到,对hash值向右移位,我理解,这样就会对高位取膜。不知道为什么要对高位取膜? |
segments | Segment<K,V>[] | 段,用来存储hashEntry数组的容器。 | |
2、内部数据结构
类名 | 用途 | 注意 |
Segment | 段,一个段是一个锁,段的个数就是可并发数,ConcurrentHashMap由segement数组组成。 | |
HashEntry | 段内由HashEntry数组组成 | HashEntry的next属性是final的,插入只能从头部开始插入,删除的时候,需要复制删除节点前面的节点,不能修改next值。为了写一致? |
扫描二维码关注公众号,回复:
609528 查看本文章
3、主要方法
序号 | 方法名 | 作用 | 注意项 | 疑问 |
1 | put(K key, V value) | 向map里面添加元素,主要看segement的put方法。 | 1、value不能为null 2、使用时对segement会加锁。 |
|
putIfAbsent(K key, V value) | 如果key存在不用传入value覆盖oldValue | |||
rehash() | segement中某个链表大小大于阈值,数组长度乘以2。 |
rehash方法会遍历旧的数组,先找到链表最后一个节点,然后从头遍历clone到新的数组中,因为HahsEntry的next属性是final的所以只能clone,如果我来做会直接遍历链表所有节点克隆到新的数组中,但是源码是先找到最后一个节点然后clone最后一个之上的所有节点,最后一个节点先放到数组中,这样做的好处是节省了空间,旧数组上每个链表最后一个节点不用克隆,坏处是链表需要遍历两次。不知道为什么作者这么选择? | ||
2 | get(Object key) | 根据key值获取Value | 1、不用加锁 2、如果取到null需要加锁重取 |
如果取到null需要加锁重取? |
3 | remove(Object key) | 根据key值删除节点 | 1、加锁 2、删除节点后,节点前的节点会倒序。 |
|
4 | size() | 获取map元素个数 | 1、size会查两遍,两遍不一样,就重试一次,一样直接退出。如果重试也不一致,则加锁重试。 2、有序加锁 |
|
5 | containsKey(Object key) | 查看指定对象是否是map中的key | 1、不加锁 | |
6 | containsValue(Object value) | map中存在指定value返回true | 1、value 不允许为null 2、遍历map,如果检测到value存在则返回true,如果不存在,查看是否有变动,如果有变动再查一次,还查不到,并且有变动,则加锁再查一次。 |
|
(1)put
先算出hash值,根据hash值定位到segement,再用segement的put方法设置值。
/** * Maps the specified key to the specified value in this table. * Neither the key nor the value can be null. * * <p> The value can be retrieved by calling the <tt>get</tt> method * with a key that is equal to the original key. * * @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 */ public V put(K key, V value) { if (value == null) throw new NullPointerException(); int hash = hash(key.hashCode()); return segmentFor(hash).put(key, hash, value, false); }
segement.put
V put(K key, int hash, V value, boolean onlyIfAbsent) {
lock();
try {
int c = count;
if (c++ > threshold) // ensure capacity
rehash();
HashEntry<K,V>[] tab = table;
int index = hash & (tab.length - 1);
HashEntry<K,V> first = tab[index];
HashEntry<K,V> e = first;
while (e != null && (e.hash != hash || !key.equals(e.key)))
e = e.next;
V oldValue;
if (e != null) {
oldValue = e.value;
if (!onlyIfAbsent)
e.value = value;
}
else {
oldValue = null;
++modCount;
tab[index] = new HashEntry<K,V>(key, hash, first, value);
count = c; // write-volatile
}
return oldValue;
} finally {
unlock();
}
}
void rehash() {
HashEntry<K,V>[] oldTable = table;
int oldCapacity = oldTable.length;
if (oldCapacity >= MAXIMUM_CAPACITY)
return;
/*
* Reclassify nodes in each list to new Map. Because we are
* using power-of-two expansion, the elements from each bin
* must either stay at same index, or move with a power of two
* offset. We eliminate unnecessary node creation by catching
* cases where old nodes can be reused because their next
* fields won't change. Statistically, at the default
* threshold, only about one-sixth of them need cloning when
* a table doubles. The nodes they replace will be garbage
* collectable as soon as they are no longer referenced by any
* reader thread that may be in the midst of traversing table
* right now.
*/
HashEntry<K,V>[] newTable = HashEntry.newArray(oldCapacity<<1);
threshold = (int)(newTable.length * loadFactor);
int sizeMask = newTable.length - 1;
for (int i = 0; i < oldCapacity ; i++) {
// We need to guarantee that any existing reads of old Map can
// proceed. So we cannot yet null out each bin.
HashEntry<K,V> e = oldTable[i];
if (e != null) {
HashEntry<K,V> next = e.next;
int idx = e.hash & sizeMask;
// Single node on list
//next为null直接设置到新数组上去,新数组上只会比原数组组员少。所以如果单节点肯定在新数组上还是单节点。
if (next == null)
newTable[idx] = e;
else {
// Reuse trailing 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 all remaining nodes
for (HashEntry<K,V> p = e; p != lastRun; p = p.next) {
int k = p.hash & sizeMask;
HashEntry<K,V> n = newTable[k];
newTable[k] = new HashEntry<K,V>(p.key, p.hash,
n, p.value);
}
}
}
}
table = newTable;
}
(2)get方法
/** * Returns the value to which the specified key is mapped, * or {@code null} if this map contains no mapping for the key. * * <p>More formally, if this map contains a mapping from a key * {@code k} to a value {@code v} such that {@code key.equals(k)}, * then this method returns {@code v}; otherwise it returns * {@code null}. (There can be at most one such mapping.) * * @throws NullPointerException if the specified key is null */ public V get(Object key) { int hash = hash(key.hashCode()); return segmentFor(hash).get(key, hash); }
/* Specialized implementations of map methods */ V get(Object key, int hash) { if (count != 0) { // read-volatile HashEntry<K,V> e = getFirst(hash); while (e != null) { if (e.hash == hash && key.equals(e.key)) { V v = e.value; if (v != null) return v; //java内存模型初始化的无序性,为什么要加锁,我想是为了确保初始化完成。 return readValueUnderLock(e); // recheck } e = e.next; } } return null; }
(3)remove 方法
/** * 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.hashCode()); return segmentFor(hash).remove(key, hash, null); }segement.remove
/** * Remove; match on key only if value null, else match both. */ V remove(Object key, int hash, Object value) { lock(); try { int c = count - 1; HashEntry<K,V>[] tab = table; int index = hash & (tab.length - 1); HashEntry<K,V> first = tab[index]; HashEntry<K,V> e = first; while (e != null && (e.hash != hash || !key.equals(e.key))) e = e.next; V oldValue = null; if (e != null) { V v = e.value; if (value == null || value.equals(v)) { oldValue = v; // All entries following removed node can stay // in list, but all preceding ones need to be // cloned. ++modCount; HashEntry<K,V> newFirst = e.next; for (HashEntry<K,V> p = first; p != e; p = p.next) newFirst = new HashEntry<K,V>(p.key, p.hash, newFirst, p.value); tab[index] = newFirst; count = c; // write-volatile } } return oldValue; } finally { unlock(); } }(4)size方法
/** * Returns the number of key-value mappings in this map. If the * map contains more than <tt>Integer.MAX_VALUE</tt> elements, returns * <tt>Integer.MAX_VALUE</tt>. * * @return the number of key-value mappings in this map */ public int size() { final Segment<K,V>[] segments = this.segments; long sum = 0; long check = 0; int[] mc = new int[segments.length]; // Try a few times to get accurate count. On failure due to // continuous async changes in table, resort to locking. for (int k = 0; k < RETRIES_BEFORE_LOCK; ++k) { check = 0; sum = 0; int mcsum = 0; for (int i = 0; i < segments.length; ++i) { sum += segments[i].count; mcsum += mc[i] = segments[i].modCount; } if (mcsum != 0) { for (int i = 0; i < segments.length; ++i) { check += segments[i].count; if (mc[i] != segments[i].modCount) { check = -1; // force retry break; } } } if (check == sum) break; } if (check != sum) { // Resort to locking all segments sum = 0; for (int i = 0; i < segments.length; ++i) segments[i].lock(); for (int i = 0; i < segments.length; ++i) sum += segments[i].count; for (int i = 0; i < segments.length; ++i) segments[i].unlock(); } if (sum > Integer.MAX_VALUE) return Integer.MAX_VALUE; else return (int)sum; }(5)containsKey
boolean containsKey(Object key, int hash) { if (count != 0) { // read-volatile HashEntry<K,V> e = getFirst(hash); while (e != null) { if (e.hash == hash && key.equals(e.key)) return true; e = e.next; } } return false; }(6)containsValue(Object value)
/** * Returns <tt>true</tt> if this map maps one or more keys to the * specified value. Note: This method requires a full internal * traversal of the hash table, and so is much slower than * method <tt>containsKey</tt>. * * @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) { if (value == null) throw new NullPointerException(); // See explanation of modCount use above final Segment<K,V>[] segments = this.segments; int[] mc = new int[segments.length]; // Try a few times without locking for (int k = 0; k < RETRIES_BEFORE_LOCK; ++k) { int sum = 0; int mcsum = 0; for (int i = 0; i < segments.length; ++i) { int c = segments[i].count; mcsum += mc[i] = segments[i].modCount; if (segments[i].containsValue(value)) return true; } boolean cleanSweep = true; if (mcsum != 0) { for (int i = 0; i < segments.length; ++i) { int c = segments[i].count; if (mc[i] != segments[i].modCount) { cleanSweep = false; break; } } } if (cleanSweep) return false; } // Resort to locking all segments for (int i = 0; i < segments.length; ++i) segments[i].lock(); boolean found = false; try { for (int i = 0; i < segments.length; ++i) { if (segments[i].containsValue(value)) { found = true; break; } } } finally { for (int i = 0; i < segments.length; ++i) segments[i].unlock(); } return found; }
四、优缺点
(1)优点
线程安全,并发性能好,竞争激烈的情况下,性能远大于HashTable
五、应用场景
需要线程安全高并发Map的场景
六、注意项
key不能为null
value不能为null