并发容器ConcurrentHashMap( 二 )

ConcurrentHashMap JDK 1.7/JDK 1.8

  • JDK 1.7 中使用分段锁(ReentrantLock + Segment + HashEntry) , 相当于把一个 HashMap 分成多个段 , 每段分配一把锁 , 这样支持多线程访问 。 锁粒度:基于 Segment , 包含多个 HashEntry 。
  • JDK 1.8 中使用 CAS + synchronized + Node + 红黑树 。 锁粒度:Node(首结点)(实现 Map.Entry) 。 锁粒度降低了 。
JDK 1.7 结构
并发容器ConcurrentHashMap文章插图
JDK 1.7 中的 ConcurrentHashMap 内部进行了 Segment 分段 ,Segment 继承了 ReentrantLock, 可以理解为一把锁 , 各个 Segment 之间都是相互独立上锁的 , 互不影响 。
相比于之前的 Hashtable 每次操作都需要把整个对象锁住而言 , 大大提高了并发效率 。 因为它的锁与锁之间是独立的 , 而不是整个对象只有一把锁 。
每个 Segment 的底层数据结构与 HashMap 类似 , 仍然是数组和链表组成的拉链法结构 。 默认有 0~15 共 16 个 Segment, 所以最多可以同时支持 16 个线程并发操作(操作分别分布在不同的 Segment 上) 。 16 这个默认值可以在初始化的时候设置为其他值 , 但是一旦确认初始化以后 , 是不可以扩容的 。
JDK 1.8 结构
并发容器ConcurrentHashMap文章插图
图中的节点有三种类型:
HashMapConcurrentHashMap链表长度大于某一个阈值(默认为 8) , 满足容量从链表的形式转化为红黑树的形式 。
红黑树是每个节点都带有颜色属性的二叉查找树 , 颜色为红色或黑色 , 红黑树的本质是对二叉查找树 BST 的一种平衡策略 , 我们可以理解为是一种平衡二叉查找树 , 查找效率高 , 会自动平衡 , 防止极端不平衡从而影响查找效率的情况发生 , 红黑树每个节点要么是红色 , 要么是黑色 , 但根节点永远是黑色的 。
ConcurrentHashMap 源码常量说明/*** table 桶数最大值 , 且前两位用作控制标志*/private static final int MAXIMUM_CAPACITY = 1 << 30;/*** table 桶数初始化默认值 , 需为2的幂次方*/private static final int DEFAULT_CAPACITY = 16;/*** 数组可能最大值 , 需要与toArray()相关方法关联*/static final int MAX_ARRAY_SIZE = Integer.MAX_VALUE - 8;/***并发级别 , 遗留下来的 , 为兼容以前的版本*/private static final int DEFAULT_CONCURRENCY_LEVEL = 16;/*** 加载因子 , 扩容的阀值 , 可在构造方法定义*/private static final float LOAD_FACTOR = 0.75f;/*** 链表转红黑树阀值, 超过 8 链表转换为红黑树*/static final int TREEIFY_THRESHOLD = 8;/*** 树转链表阀值 , 小于等于6(tranfer时 , lc、hc=0两个计数器分别++记录原bin、新binTreeNode数量 , <=UNTREEIFY_THRESHOLD 则untreeify(lo))*/static final int UNTREEIFY_THRESHOLD = 6;/*** 树化阀值2 , 当数组桶树达到64以上才允许链表树化*/static final int MIN_TREEIFY_CAPACITY = 64;初始化 tableprivate final Node[] initTable() {Node[] tab; int sc;// 初始化成功退出循环while ((tab = table) == null || tab.length == 0) {if ((sc = sizeCtl) < 0)Thread.yield(); // 有其他线程在初始化 , 自旋等待else if (U.compareAndSwapInt(this, SIZECTL, sc, -1)) {// 进入初始化 sizeCtl = -1try {if ((tab = table) == null || tab.length == 0) {int n = (sc > 0) ? sc : DEFAULT_CAPACITY;Node[] nt = (Node[])new Node[n];table = tab = nt;// sc = n-n/4 ???sc = n - (n >>> 2);}} finally {// 初始化成功设置 sizeCtlsizeCtl = sc;}break;}}return tab;}


推荐阅读