ConcurrentHashMap的设计

1.7和1.8的结构差异

1.7

  1. 结构
    segment[]+hashEntry[],每个segment继承reentrantlock
  2. 并发度
    默认16个segment,理论最大并发16
  3. 缺陷
    并发度固定,两次哈希查询慢

1.8

  1. 结构
    Node[]+链表+红黑树,类似hashmap
  2. 并发度
    动态,理论可达数组长度
  3. 优势
    一次哈希,红黑树优化(O(log n))

为什么能抛弃 Segment?

  1. CAS的成熟
    CPU指令毫秒级完成
  2. synchorized升级
    偏向锁—>轻量锁->重量锁,性能不亚于ReentrantLock
  3. 红黑树引入
    哈希冲突时查询O(lon n)
  4. 分段计数
    LongAdder思想,无锁统计size

put 方法在并发下具体是怎么保证线程安全的(

CAS + synchronized 的三道防线

for (;;) {  // 自旋
    if (tab == null) initTable();          // ① CAS 初始化
    else if (桶为空) casTabAt(插入);        // ② CAS 无锁插入(性能最高)
    else if (扩容中) helpTransfer();        // ③ 协助并发扩容
    else synchronized(头节点) { 插入链表/树 } // ④ 细粒度锁桶
}
  1. 场景一:数组初始化
    CAS更新sizeCtl,(sizeCtl是表示目前数组状态的字段,volite修饰,扩容中,初始化等等)
  2. 场景二:桶为空
    CAS插入首节点,无锁、极速
  3. 场景三:桶非空
    synchronized 锁头节点,粒度最细,不同桶互不影响

get 为什么全程不用加锁?

get方法不加锁靠三层机制保障

  1. volatile
    Node.val和next用volatile修饰,写立即可见
  2. final
    Node.key和hash不可变
  3. Happen-before
    volatile写在读之前发送

size():分段计数,无锁统计

volatile long baseCount;           // 基础计数
volatile CounterCell[] counterCells; // 辅助计数槽(≤ CPU核心数)
volatile int cellsBusy;            // 扩容控制(CAS)
  1. 流程一
    优先CAS更新BaseCount
  2. 流程二
    通过线程的Probe随机定位到某个CounterCell槽位,CAS累加
  3. 流程三
    再失败,扩容CounterCell或更换Probe重试