ConcurrentHashMap
public class ConcurrentHashMap<K,V> extends AbstractMap<K,V> implements ConcurrentMap<K,V>, Serializable {}
可以看到,继承自AbstractMap, 实现了ConcurrentMap,以及可序列化接口。
先看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 {@code get} 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 {@code key}, or * {@code null} if there was no mapping for {@code key} * @throws NullPointerException if the specified key or value is null */ public V put(K key, V value) { return putVal(key, value, false); } /** Implementation for put and putIfAbsent */ final V putVal(K key, V value, boolean onlyIfAbsent) { //key value 不允许为null if (key == null || value == null) throw new NullPointerException(); //计算key的hash值 int hash = spread(key.hashCode()); //这个数值不知道用来干啥的 int binCount = 0; for (Node<K,V>[] tab = table;;) { //f是当前key值获取到的桶的首元素,n是数组的长度,即桶数量,i是数组索引,fh是f的hash值 Node<K,V> f; int n, i, fh; if (tab == null || (n = tab.length) == 0) //初始化容器 tab = initTable(); else if ((f = tabAt(tab, i = (n - 1) & hash)) == null) { //当table没有对应的索引桶,放入新数据的步骤,使用cas操作,可以实现无锁化 if (casTabAt(tab, i, null, new Node<K,V>(hash, key, value, null))) break; // no lock when adding to empty bin } else if ((fh = f.hash) == MOVED) //Helps transfer if a resize is in progress. 不知道干啥,先放着 tab = helpTransfer(tab, f); else { //数组非空,并且对应索引桶也非空,遍历桶,以添加新节点或者覆盖旧节点 V oldVal = null; synchronized (f) { if (tabAt(tab, i) == f) { if (fh >= 0) { //node是普通的节点 binCount = 1; for (Node<K,V> e = f;; ++binCount) { K ek; if (e.hash == hash && ((ek = e.key) == key || (ek != null && key.equals(ek)))) { //hash值和key都相等,找到重复的键值 oldVal = e.val; if (!onlyIfAbsent) e.val = value; break; } Node<K,V> pred = e; if ((e = e.next) == null) { //遍历到达链表的末尾,仍然没找到重复的键值,添加新的node pred.next = new Node<K,V>(hash, key, value, null); break; } } } else if (f instanceof TreeBin) { //红黑树结构 Node<K,V> p; binCount = 2; if ((p = ((TreeBin<K,V>)f).putTreeVal(hash, key, value)) != null) { oldVal = p.val; if (!onlyIfAbsent) p.val = value; } } } } if (binCount != 0) { if (binCount >= TREEIFY_THRESHOLD) treeifyBin(tab, i); if (oldVal != null) return oldVal; break; } } } addCount(1L, binCount); return null; }
通过查看put方法,大概了解其步骤如下:
当数组为空时,初始化数组结构,初始化步骤包含了自旋(通过判断数组是否为空,以及是否允许初始化的状态位)以及cas操作,以保证只有一个线程能够初始化成功,防止多次初始化。
数组初始化成功后,计算key值的hash,通过hash对数组长度求余,获取数组索引值,通过索引获取数组的桶的首节点数据
如果桶首元素为空,通过cas操作对空桶新增首元素。这里也是通过自旋,以及cas操作保证线程安全性,只有一个线程能够对空桶新增首元素。
如果桶首元素不为空,并且数组正在transfer中(我的理解transfer是重构数组结构中,这个可以通过节点的hash值来判断,负数的hash值有特殊含义),则帮忙transfer(这个步骤,初步理解是这样的:transfer操作比较耗时,其他线程的帮忙,可以加快速度)
如果桶首元素不为空,数组没有transfer,则锁定(synchronized)桶首元素,执行以下步骤
如果桶数据结构为普通链表,遍历链表,根据遍历结果,新增节点或者覆盖旧节点
如果桶数据结构为红黑树,遍历红黑树,根据遍历结果,新增节点或者覆盖旧节点
最后判断新增节点后,是否需要将对应桶的链表(如果是链表的话)结构转换成红黑树,然后将旧元素的节点数据的value值返回(没有旧元素则返回null)
其中,计算key的hash值的方法如下:
static final int HASH_BITS = 0x7fffffff; // usable bits of normal node hash static final int spread(int h) { return (h ^ (h >>> 16)) & HASH_BITS; }
而HashMap的计算Hash值的方法如下:
static final int hash(Object key) { int h; return (key == null) ? 0 : (h = key.hashCode()) ^ (h >>> 16); }
区别如下:
前者(ConcurrentHashMap)不需要考虑key为null的情况,因为不允许key和value为null。
前者比后者计算多出来一步,即 “&HASH_BITS” 的操作 ,该操作的作用是把最高位(即符号位)设置为0。
对比jdk1.7和jdk1.8的实现,1.8弃用了1.7的Segment的概念,也就是分段锁。
1.7的分段锁Sement继承了Lock类,可以对每个Segment进行锁定,而其实每个Segment就是数组中的多个桶的集合,其锁定粒度,既不会太大,也不会太小,相对于HashTable的直接锁定整个数组而言,是一种优化。在1.8中被放弃的原因,个人理解如下:
jdk1.8中,ConcurrentHashMap用synchronized和cas取代Lock来保证线程安全。cas不用说,只要竞争不太激烈,是一种较高效的无锁化机制,然后Synchronized是jdk内置的关键字,JVM可以对其进行优化,而Lock类对象则不行。这里synchronized的优化涉及到锁升级,锁消除等。从轻量到重量,分为:偏向锁(无锁化)、轻量级锁(CAS,自旋),重量级锁(竞争的线程进入阻塞状态。并且操作系统在加锁和释放锁过程,需要在用户态和核心态之间进行切换,因为该操作依赖于操作系统的互斥锁,开销很大。当前线程释放锁后,需要从阻塞队列的对头拿到下一个线程,并进行唤醒操作)。
1.8中,是对数组的每个桶进行加锁,相比Segment,锁粒度更小,并发度更高了。
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) { //tab是数组,e是索引桶的首节点,p是,n是数组长度,eh是e的hash值,ek是e的key Node<K,V>[] tab; Node<K,V> e, p; int n, eh; K ek; //求key的hash值 int h = spread(key.hashCode()); if ((tab = table) != null && (n = tab.length) > 0 && (e = tabAt(tab, (n - 1) & h)) != null) { //数组不为空,索引桶也不为空 if ((eh = e.hash) == h) { if ((ek = e.key) == key || (ek != null && key.equals(ek))) //满足条件,返回桶首节点 return e.val; } else if (eh < 0) //hash值小于0,特殊节点处理(比如红黑树结构) return (p = e.find(h, key)) != null ? p.val : null; while ((e = e.next) != null) { //遍历链表所有节点 if (e.hash == h && ((ek = e.key) == key || (ek != null && key.equals(ek)))) return e.val; } } return null; }
源码对应java版本:
>java -version
java version "1.8.0_102"
Java(TM) SE Runtime Environment (build 1.8.0_102-b14)
Java HotSpot(TM) 64-Bit Server VM (build 25.102-b14, mixed mode)