JDK7ConcurrentHashMap结构

ConcurrentHashMap源码简单分析_HashMap
7中本质上还是采用链表+数组的形式存储键值对的。但是,为了提高并发,把原来的整个 table 划分为 nSegment 。所以,从整体来看,它是一个由 Segment 组成的数组。然后,每个 Segment 里边是由 HashEntry 组成的数组,每个 HashEntry 之间又可以形成链表。我们可以把每个 Segment 看成是一个小的 HashMap,其内部结构和 HashMap 是一模一样的。

当对某个 Segment 加锁时,并不会影响到其他 Segment 的读写。每个 Segment 内部自己操作自己的数据。这样一来,我们要做的就是尽可能的让元素均匀的分布在不同的 Segment 中。最理想的状态是,所有执行的线程操作的元素都是不同的 Segment,这样就可以降低锁的竞争。

JDK8ConcurrentHashMap结构

先看put方法的源码

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)
        	// 首次put的时候会先去初始化hash桶
            tab = initTable();
        else if ((f = tabAt(tab, i = (n - 1) & hash)) == null) {
        	// 如果该位置还为null,说明该位置上还没有记录,
        	// 则通过调用casTabAt方法来讲该新的记录插入到table的index位置上去
            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关键字对table的index位置加锁
            // 当前线程只会锁住table的index位置,其他位置上没有锁住
            synchronized (f) {
                if (tabAt(tab, i) == f) {
                    if (fh >= 0) {
                    	// 判断table的index位置上的第一个节点的hashCode值,大于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)))) {
                                // 寻找是否已经有记录的key和当前想要插入的记录是一致的,如果一致,
                                // 那么这次put的效果就是replace
                                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) {
                   	 // 判断table的index位置上的第一个节点的hashCode值,小于0 说明是一个红黑树
                        Node<K,V> p;
                        binCount = 2;
                        // 调用putTreeVal方法来进行插入操作
                        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方法的逻辑是这样的

  1. 计算记录的keyhashCode
  2. 计算tableindex位置,如果该位置还为null,说明该位置上还没有记录
  3. 通过调用casTabAt方法来将该新的记录插入到tableindex位置上去
  4. 如果该位置不为null,通过synchronized关键字对tableindex位置加锁,然后判断tableindex位置上的第一个节点的hashCode值,这个值大于零则说明此处是一个链表,小于0说明是一个红黑树
  5. 执行链表或者红黑树的插入节点方法

看起来JDK8中的ConcurrentHashMap结构和HashMap结构几乎是一样的,区别就在于插入的时候会对节点进行加锁。相对于 1.7 来说,锁的粒度降低了,效率也提高了。