ConcurrentHashMap的实现原理是分段锁?你Out了

Posted 沛沛老爹

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了ConcurrentHashMap的实现原理是分段锁?你Out了相关的知识,希望对你有一定的参考价值。

前言

Java后端开发面试的时候,一场好的面试,是无论如何也绕不开并发编程的。并发编程里面往往有个很重要的类可能会被拿出来探讨:ConcurrentHashMap。
ConcurrentHashMap是HashMap的线程安全版。大家都知道HashMap的高性能,但是HashMap不是线程安全的。所以为了解决这个问题,就推出了Concurrent版本。

在和面试官探讨这个问题的时候,大家一般都会把分段锁Segment的实现原理拿出来大讲特讲(因为除了这个分段式锁外,好像别的也没有什么不同的)。

模拟面试场景

面试官:请简述下ConcurrentHashMap的实现原理。
面试者
  1、ConcurrentHashMap是由Segment数组和HashEntry数组组成。它内部细分成了若干个小的HashMap,每个小的HashMap被称为段(Segment),默认情况下,一个ConcurrentHashMap被细分为16个Segment。
  2、Segment是一种可重入锁ReentrantLock,它在ConcurrentHashMap中扮演锁的角色;HashEntry用来存储键值对数据。
  3、一个 ConcurrentHashMap 里包含一个 Segment 数组,Segment 的结构和 HashMap类似,是一种数组+链表结构。
  4、每个Segment里包含一个HashEntry数组,当要对HashEntry数组的数据进行修改时,必须要先获得它对应的Segment锁。

真正的ConcurrentHashMap

此时,是不是感觉自己答的很溜?这个问题中的重要点都回答了。
但是,这真的是你用的ConcurrentHashMap吗?还是你Out了?

其实,你还真是Out了。上面的回答是没问题的,但是知识点Out了。这个ConcurrentHashMap分段锁的实现是JDK1.7的了(网上的很多知识都是这样的)。JDK1.8之后的实现有了很大的改变了

ConcurrentHashMap源码

说多了浪费口水,还是直接看源码吧。
ConcurrentHashMap代码在java.util.concurrent包中(不要搞错了)

Node部分源码

public class ConcurrentHashMap<K,V> extends AbstractMap<K,V>
    implements ConcurrentMap<K,V>, Serializable 
	 	/* ---------------- Constants -------------- */
		//...  省略部分代码
	
	//实现重点代码
	/* ---------------- Nodes -------------- */
    /**
     * Key-value entry.  This class is never exported out as a
     * user-mutable Map.Entry (i.e., one supporting setValue; see
     * MapEntry below), but can be used for read-only traversals used
     * in bulk tasks.  Subclasses of Node with a negative hash field
     * are special, and contain null keys and values (but are never
     * exported).  Otherwise, keys and vals are never null.
     */
    static class Node<K,V> implements Map.Entry<K,V> 
        final int hash;
        final K key;
        volatile V val;
        volatile Node<K,V> next;

        Node(int hash, K key, V val, Node<K,V> next) 
            this.hash = hash;
            this.key = key;
            this.val = val;
            this.next = next;
        

        public final K getKey()        return key; 
        public final V getValue()      return val; 
        public final int hashCode()    return key.hashCode() ^ val.hashCode(); 
        public final String toString() return key + "=" + val; 
        public final V setValue(V value) 
            throw new UnsupportedOperationException();
        

        public final boolean equals(Object o) 
            Object k, v, u; Map.Entry<?,?> e;
            return ((o instanceof Map.Entry) &&
                    (k = (e = (Map.Entry<?,?>)o).getKey()) != null &&
                    (v = e.getValue()) != null &&
                    (k == key || k.equals(key)) &&
                    (v == (u = val) || v.equals(u)));
        

        /**
         * Virtualized support for map.get(); overridden in subclasses.
         */
        Node<K,V> find(int h, Object k) 
            Node<K,V> e = this;
            if (k != null) 
                do 
                    K ek;
                    if (e.hash == h &&
                        ((ek = e.key) == k || (ek != null && k.equals(ek))))
                        return e;
                 while ((e = e.next) != null);
            
            return null;
        
    
 //...  省略部分代码

我们会发现,ConcurrentHashMap类中中没有找到Segment的实现的影子。而是在类中发现了很多使synchronized的方式来实现的。

put源码

我们看下常用的put方法的实现。发现使用的是synchronized来实现的。

	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) 
        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 (casTabAt(tab, i, null, r)) 
	          //...省略代码
    
···

CAS部分代码

    static final <K,V> boolean casTabAt(Node<K,V>[] tab, int i,
                                        Node<K,V> c, Node<K,V> v) 
        return U.compareAndSwapObject(tab, ((long)i << ASHIFT) + ABASE, c, v);
    

想要了解更多CAS部分的知识,可以参考这篇文章CAS(Compare and swap)比较并交换算法解析

总结

新版的ConcurrentHashMap和Segment分段锁没有任何关系。它的实现方式和HashMap有点大同小异:数组+链表+红黑树。

更新后的变化

类型JDK1.7JDK1.8
数据结构Segment分段锁数组+链表+红黑树
线程安全机制segment的分段锁机制CAS+Synchorized机制
锁的粒度每段Segment加锁,粒度大每个Node元素加锁,粒度更细
遍历时间复杂度O(n)O(logN)

好了,如果你把上面新的知识结合老的知识一起给到面试官,我想面试官肯定会想,这家伙是个可造之才,让HR通知他,明天赶紧来上班。

以上是关于ConcurrentHashMap的实现原理是分段锁?你Out了的主要内容,如果未能解决你的问题,请参考以下文章

ConcurrentHashMap的实现原理是分段锁?你Out了

一文就懂ConcurrentHashMap实现原理

一文就懂ConcurrentHashMap实现原理

ConcurrentHashMap面试题

十二ConcurrentHashMap的实现原理解析

ConcurrentHashMap实现原理