java并发系列—concurrenthashmap详解(上)

三种集合:
  • HashMap 是线程不安全的,性能好
  • Hashtable 线程安全基于 synchronized,综合性能差,已经被淘汰
  • ConcurrentHashMap 保证了线程安全,综合性能较好,不止线程安全,而且效率高,性能好

集合对比:

  1. Hashtable 继承 Dictionary 类,HashMap、ConcurrentHashMap 继承 AbstractMap,均实现 Map 接口
  2. Hashtable 底层是数组 + 链表,JDK8 以后 HashMap 和 ConcurrentHashMap 底层是数组 + 链表 + 红黑树
  3. HashMap 线程非安全,Hashtable 线程安全,Hashtable 的方法都加了 synchronized 关来确保线程同步
  4. ConcurrentHashMap、Hashtable 不允许 null 值,HashMap 允许 null 值
  5. ConcurrentHashMap、HashMap 的初始容量为 16,Hashtable 初始容量为11,填充因子默认都是 0.75,两种 Map 扩容是当前容量翻倍:capacity * 2,Hashtable 扩容时是容量翻倍 + 1:capacity*2 + 1ConcurrentHashMap数据结构
JDK 7 HashMap 并发死链(结合视频)
JDK 7 HashMap 并发死链(结合视频)

简单的说:不同线程调用map时,由于头插法的因素产生了死链结构,

JDK 8 虽然将扩容算法做了调整,不再将元素加入链表头(而是保持与扩容前一样的顺序),但仍不意味着能够在多线程环境下能够安全扩容,还会出现其它问题(如扩容丢数据

单词计数问题

结论:线程安全类的集合指的是,单个方法被多个线程同时调用是线程安全的,但在临界区中同时使用了多个方法不一定是线程安全的

重要属性
  • 控制变量:

    sizeCtl < 0:

    • -1 表示当前 table 正在初始化(有线程在创建 table 数组),当前线程需要自旋等待

    • 其他负数表示当前 map 的 table 数组正在进行扩容,高 16 位表示扩容的标识戳;低 16 位表示 (1 + nThread) 当前参与并发扩容的线程数量 + 1

    sizeCtl = 0,表示创建 table 数组时使用 DEFAULT_CAPACITY 为数组大小

    sizeCtl > 0:

    • 如果 table 未初始化,表示初始化大小
    • 如果 table 已经初始化,表示下次扩容时的触发条件(阈值,元素个数,不是数组的长度)
    private transient volatile int sizeCtl;		// volatile 保持可见性
    
  • 存储数组:

    transient volatile Node<K,V>[] table;
    
  • 负载因子,JDK1.8 的 ConcurrentHashMap 中是固定值:

    private static final float LOAD_FACTOR = 0.75f;
    
  • 阈值:

    static final int TREEIFY_THRESHOLD = 8;		// 链表树化的阈值
    static final int UNTREEIFY_THRESHOLD = 6;	// 红黑树转化为链表的阈值
    static final int MIN_TREEIFY_CAPACITY = 64;	// 当数组长度达到64且某个桶位中的链表长度超过8,才会真正树化
    
  • 节点哈希值:

    static final int MOVED     = -1; 			// 表示当前节点是 FWD 节点
    static final int TREEBIN   = -2; 			// 表示当前节点已经树化,且当前节点为 TreeBin 对象
    static final int RESERVED  = -3; 			// 表示节点时临时节点
    static final int HASH_BITS = 0x7fffffff; 	// 正常节点的哈希值的可用的位数
    
  • 扩容过程:volatile 修饰保证多线程的可见性

    // 扩容过程中,会将扩容中的新 table 赋值给 nextTable 保持引用,扩容结束之后,这里会被设置为 null
    private transient volatile Node<K,V>[] nextTable;
    // 记录扩容进度,所有线程都要从 0 - transferIndex 中分配区间任务,简单说就是老表转移到哪了,索引从高到低转移
    private transient volatile int transferIndex;
    
  • 累加统计:

    // LongAdder 中的 baseCount 未发生竞争时或者当前LongAdder处于加锁状态时,增量累到到 baseCount 中
    private transient volatile long baseCount;
    // LongAdder 中的 cellsBuzy,0 表示当前 LongAdder 对象无锁状态,1 表示当前 LongAdder 对象加锁状态
    private transient volatile int cellsBusy;
    // LongAdder 中的 cells 数组,
    private transient volatile CounterCell[] counterCells;
    
内部类
  • Node 节点:

    static class Node<K,V> implements Entry<K,V> {
        // 节点哈希值
        final int hash;
        final K key;
        volatile V val;
        // 单向链表
        volatile Node<K,V> next;
    }
    
  • TreeBin 节点:

     static final class TreeBin<K,V> extends Node<K,V> {
         // 红黑树根节点
         TreeNode<K,V> root;
         // 链表的头节点
         volatile TreeNode<K,V> first;
         // 等待者线程
         volatile Thread waiter;
    
         volatile int lockState;
         // 写锁状态 写锁是独占状态,以散列表来看,真正进入到 TreeBin 中的写线程同一时刻只有一个线程
         static final int WRITER = 1;
         // 等待者状态(写线程在等待),当 TreeBin 中有读线程目前正在读取数据时,写线程无法修改数据
         static final int WAITER = 2;
         // 读锁状态是共享,同一时刻可以有多个线程 同时进入到 TreeBi 对象中获取数据,每一个线程都给 lockState + 4
         static final int READER = 4;
     }
    
  • TreeNode 节点:

    static final class TreeNode<K,V> extends Node<K,V> {
        TreeNode<K,V> parent;  // red-black tree links
        TreeNode<K,V> left;
        TreeNode<K,V> right;
        TreeNode<K,V> prev;   //双向链表
        boolean red;
    }
    
  • ForwardingNode 节点:转移节点

     static final class ForwardingNode<K,V> extends Node<K,V> {
         // 持有扩容后新的哈希表的引用
         final Node<K,V>[] nextTable;
         ForwardingNode(Node<K,V>[] tab) {
             // ForwardingNode 节点的 hash 值设为 -1
             super(MOVED, null, null, null);
             this.nextTable = tab;
         }
     }
    
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值