ConcurrentHashMap源码解析

CHM 的使用

ConcurrentHashMap是 J.U.C 包里面提供的一个线程安全并且高效的 HashMap,所以ConcurrentHashMap 在并发编程的场景中使用的频率比较高,那么我们就从ConcurrentHashMap 的使用上以及源码层面来分析 ConcurrentHashMap到底是如何实现安全性的。

api 使用

ConcurrentHashMapMap的派生类,所以 api 基本和 Hashmap是类似,主要就是 putget 这些方法,接下来基于 ConcurrentHashMapputget这两个方法作为切入点来分析 ConcurrentHashMap 的源码实现。

ConcurrentHashMap 的源码分析

先要做一个说明,这节课分析的 ConcurrentHashMap是基于 jdk1.8 的版本。
ConcurrentHashMapHashMap 的实现原理是差不多的,但是因为 ConcurrentHashMap需要支持并发操作,所以在实现上要比 Hashmap稍微复杂一些。在 JDK1.7 的 实 现 上 , ConrruentHashMap 由 一 个 个 Segment组 成 , 简 单 来 说 ,ConcurrentHashMap 是一个 Segment数组,它通过继承 ReentrantLock来进行加锁,通过每次锁住一个 segment 来保证每个 segment 内的操作的线程安全性从而实现全局线程安全。
整个结构图如下

jdk-1.7.png

当每个操作分布在不同的 segment 上的时候,默认情况下,理论上可以同时支持 16 个线程的并发写入。
相比于 1.7 版本,它做了两个改进

  1. 取消了 segment 分段设计,直接使用 Node 数组来保存数据,并且采用 Node 数组元素作为锁来实现每一行数据进行加锁来进一步减少并发冲突的概率
  2. 将原本数组+单向链表的数据结构变更为了数组+单向链表+红黑树的结构。为什么要引入红黑树呢?在正常情况下, key hash 之后如果能够很均匀的分散在数组中,那么 table 数组中的每个队列的长度主要为 0 或者 1.但是实际情况下,还是会存在一些队列长度过长的情况。如果还采用单向列表方式,那么查询某个节点的时间复杂度就变为O(n); 因此对于队列长度超过 8的列表并且数组的长度超过64 , JDK1.8 采用了红黑树的结构,那么查询的时间复杂度就会降低到O(logN),可以提升查找的性能;
static final int TREEIFY_THRESHOLD = 8;
static final int MIN_TREEIFY_CAPACITY = 64;

jdk-1.8.png

这个结构和 JDK1.8 版本中的 Hashmap 的实现结构基本一致,但是为了保证线程安全性,ConcurrentHashMap 的实现会稍微复杂一下。接下来我们从源码层面来了解一下它的原理.我们基于 putget方法来分析它的实现即可.

put 方法第一阶段

public V put(K key, V value) {
    return putVal(key, value, false);
}
    final V putVal(K key, V value, boolean onlyIfAbsent) {
        if (key == null || value == null) throw new NullPointerException();
        int hash = spread(key.hashCode()); //计算 hash 值
        int binCount = 0; //用来记录链表的长度
        for (Node[] tab = table; ; ) { //这里其实就是自旋操作,当出现线程竞争时不断自旋
            Node f;
            int n, i, fh;
            if (tab == null || (n = tab.length) == 0)//如果数组为空,则进行数组初始 化
                tab = initTable(); //初始化数组
            //通过 hash 值对应的数组下标得到第一个节点; 以 volatile 读的方式来读取 table 数
            组中的元素,保证每次拿到的数据都是最新的
            else if ((f = tabAt(tab, i = (n - 1) & hash)) == null) {
                //如果该下标返回的节点为空,则直接通过 cas 将新的值封装成 node 插入即可;如果 cas 失败,说明存在竞争,则进入下一次循环
                if (casTabAt(tab, i, null,
                        new Node(hash, key, value, null)))
                    break; // no lock when adding to empty bin
            }
        }
    }

待续!!

你可能感兴趣的:(ConcurrentHashMap源码解析)