1. JDK 1.7版本
1.1 介绍
1.1.1 作用
ConcurrentHashMap是JUC下的一个实现类,解决高并发下HashMapd的线程安全问题,并且相对于Hashtable来说,效率更高,因为内部使用了segment分段锁,使多个线程能够同时操作,而不会引发线程安全的问题,实现方面用到了无锁的CAS(ReentrantLock)。
1.1.2 类结构分析
1.2 原理分析
1.3 源码分析
1.3.1 put
① 确定对应的segment
② 对segment加锁,获取segment中的table
③ 通过对key进行hash得到hash值,以此获取对应的table元素(HashEntry链表)
④ 如果key相同,则替换旧值,不同则新增一个HashEntry并确认是否需要扩容
⑤ 解锁
12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364 public V put(K key, V value) {Segment<K,V> s;if (value == null)//value不允许为null,HashMap则key和value都可以是null(bucket为0)throw new NullPointerException();int hash = hash(key);int j = (hash >>> segmentShift) & segmentMask;//① 确定对应的segmentif ((s = (Segment<K,V>)UNSAFE.getObject // nonvolatile; recheck(segments, (j << SSHIFT) + SBASE)) == null) // in ensureSegments = ensureSegment(j);return s.put(key, hash, value, false);}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;//以此获取对应的table元素(HashEntry链表),hash是对key进行hash运算int index = (tab.length - 1) & hash;HashEntry<K,V> first = entryAt(tab, index);//遍历HashEntry链表for (HashEntry<K,V> e = first;;) {//HashEntry不为nullif (e != null) {K k;//确认key是否相同,相同则替代旧值if ((k = e.key) == key ||(e.hash == hash && key.equals(k))) {oldValue = e.value;if (!onlyIfAbsent) {e.value = value;//修改modCount++modCount;}break;}e = e.next;}else {if (node != null)node.setNext(first);else//新增一个HashEntry对象node = new HashEntry<K,V>(hash, key, value, first);int c = count + 1;//判断是否需要扩容if (c > threshold && tab.length < MAXIMUM_CAPACITY)rehash(node);elsesetEntryAt(tab, index, node);//修改modCount++modCount;count = c;oldValue = null;break;}}} finally {unlock();}return oldValue;}
1.3.2 get
① 定位segment
② 定位HashEntry[]
③ 遍历HashEntry[],并获取key相同的值
1234567891011121314151617 public V get(Object key) {Segment<K,V> s; // manually integrate access methods to reduce overheadHashEntry<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;}
1.3.3 remove
jdk1.6需要两个链表来实现remove,而jdk1.7则不需要
1234567891011121314151617181920212223242526272829303132333435363738 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[]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;}
1.3.4 reshah扩容
① 重新新建一个ConcurrentHashMap的数据结构,将原有的值全部负责到新的数据结构中
② 扩容后容量为原来容量的两倍
|
|
1.3.5 size
size()方法首先会进行两次尝试(不加锁),两次结果的modCount都一致的话,则表示此过程中的ConcurrentHashMap并未改变,返回各个segment的count之和,即是size的值;假若modCount不一致,则对每个segment依次加锁,获取其count值,最终才是真正的size值。
|
|
2. JDK 1.8 版本浅析
2.1 介绍
1.8版本的ConcurrentHashMap做了很大的改动:
① 结构就变成了“数组+链表+红黑树”的形式
② 操作时,不再锁整个segment,而是对节点node加锁
③ 加锁采用了synchronized,而不是1.7版本中的ReentrantLock
其中,当table[i]的所代表的链表长度大于8时,就会将结构由链表改变为红黑树,此时查询时的时间复杂度由原来的O(n)变成了O(logN),提高了查询效率。
2.2 类结构分析
2.3 原理图分析
2.4 源码分析
2.4.1 put()
① 得到table[i]
② 对某节点加锁(如果key对应位置是空时,不用加锁,用CAS)
③ 首节点
3.1 首节点是链表:key存在的话,则设置value;否则新建node节点,加入链表
3.2 首节点是红黑树:则用红黑树的方式设置值
④ bitCount >= 8时,将链表转换为红黑树的结构形式
12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667 /** Implementation for put and putIfAbsent */final V putVal(K key, V value, boolean onlyIfAbsent) {if (key == null || value == null) throw new NullPointerException();int hash = spread(key.hashCode());int binCount = 0;for (Node<K,V>[] tab = table;;) {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) {//如果这个位置没有值 ,直接放进去,不需要加锁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)tab = helpTransfer(tab, f);else {V oldVal = null;//② 对某节点加锁synchronized (f) {if (tabAt(tab, i) == f) {if (fh >= 0) {binCount = 1;for (Node<K,V> e = f;; ++binCount) {K ek;if (e.hash == hash &&((ek = e.key) == key ||(ek != null && key.equals(ek)))) {oldVal = e.val;if (!onlyIfAbsent)e.val = value;break;}Node<K,V> pred = e;if ((e = e.next) == null) {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) {//④ bitCount >= 8时,将链表转换为红黑树的结构形式if (binCount >= TREEIFY_THRESHOLD)treeifyBin(tab, i);if (oldVal != null)return oldVal;break;}}}addCount(1L, binCount);return null;}