ConcurrentMap原理详解

流过昼夜 提交于 2021-01-27 09:53:41

ConcurrentMap原理详解

jdk1.8

数据结构

  • 数组+链表+红黑树

  • Node<K,V>{}

    static class Node<K,V> implements Entry<K,V> {
    final int hash;
    final K key;
    volatile V val;
    volatile Node<K,V> next;



如何线程安全

  • 数组桶值设置和更新使用CAS算法

    • tabAt(Node<K,V>[] tab, int i)
    • casTabAt(Node<K,V>[] tab, int i, Node<K,V> c, Node<K,V> v)
    • setTabAt(Node<K,V>[] tab, int i, Node<K,V> v)
  • 针对链表和树更新使用Synchronize

设置putVal(K key, V value, boolean onlyIfAbsent)

  • 1、数组为空,先初始化

  • 2、tabAt获取桶位置元素,如果为空,则casTabAt(),期望值为null,新值为新节点

  • 3、桶位置如果存在数据,说明hash冲突了,如果当前节点hash为MOVED说明在扩容,则当前线程辅助扩容

  • 4、如果hash值>=0,说明是链表结构

    • 4.1循环覆盖链表所有key一样节点
    • 4.2循环至链表尾结点时(next为null)则追加到尾结点
  • 5、如果当前节点对象属于TreeBin类型,则为红黑树

    • 设置树节点
  • 6、如果链表节点数量大于等于8时,则链表转红黑树

  • 7、检查桶节点数量是否大于阈值,否则进行扩容

获取get(不加锁)

  • 1、计算hash,tabAt获取桶位置是否为null

    • 如果桶节点key和查询key一样则返回节点对应val
    • 如果hash<0则为树节点,从树中找到key一样的值
    • 反之从链表循环判断至key一样时返回

扩容transfer(并发)

  • 扩容时机

    • put完成后检验当前桶数量是否大于阈值
    • put时新增节点时,如果节点数量大于8时转树,调用tryPresize方法,如果当前桶数量小于64时会扩容两倍,但此时不会转为树结构
    • 扩容状态下 针对增删改时,如果操作节点是 ForwardingNode节点时会调用helpTransfer 辅助扩容
  • transfer

    • 1、单线程新建一个两倍大小的节点数组

FAQ

  • 如何保证线程安全

    • unsafe cas主要负责并发修改桶位置的数据
    • synchronize保证链表或者红黑树节点更新
  • 与1.7的区别

    • 8没有使用分段锁,而是使用锁力度更细的桶锁
    • 支持多线程同时扩容,如果当前hash值为-1时,说明正在扩容,当前线程会加入帮助扩容,1.7也支持多线程扩容,因为每个分段相当于一个线程扩容,但是性能没有1.8高,因为1.8任意一个线程都会帮助扩容
    • size计算不同,jdk8使用countercell帮助计数,1.7是每个分段内部维护一个size,然后遍历每个分段加锁进行统计

jdk1.7

分段数组+链表

XMind - Trial Version

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!