ConcurrentHashMap第一讲:结构分析,变量及部分常见方法

本文深入探讨了Java并发集合ConcurrentHashMap的内部实现,包括其核心变量如容量、负载因子和树化阈值等。详细解释了如resizeStamp、tableSizeFor等关键方法的用途,以及在扩容和线程安全方面的策略。同时,介绍了LongAdder在ConcurrentHashMap中的应用,展示了其如何提高并发性能。
摘要由CSDN通过智能技术生成

在这里插入图片描述
桶下面维护的有普通链表,也有树节点(红黑树),树节点下面有树结构和双向链表

1、ConcurrentMap中的变量

部分变量:

     * 散列表数组最大值(是容量的最大值,  和size比较的)
    private static final int MAXIMUM_CAPACITY = 1 << 30;
    
     * 散列表默认值
    private static final int DEFAULT_CAPACITY = 16;
    
     * 并发级别,jdk1.7遗留下来的,1.8只有在初始化的时候用一用
     * 不代表并发级别,
     * 1.8中的并发级别是散列表的长度决定的
    private static final int DEFAULT_CONCURRENCY_LEVEL = 16;
    
     * 负载因子,是固定值,不可修改
    private static final float LOAD_FACTOR = 0.75f;
    
     * 树化阈值,指定桶位,链表长度达到8的话,有可能树化操作
    static final int TREEIFY_THRESHOLD = 8;
    
     * 红黑树转化为链表的阈值
    static final int UNTREEIFY_THRESHOLD = 6;
    
     * 联合TREEIFY_THRESHOLD 控制桶位,是否树化,只有当table数组长度达到64
     * 且 某个桶位 中的链表长度达到8,才会真正树化
    static final int MIN_TREEIFY_CAPACITY = 64;
    
     * 线程迁移数据最小步长,控制线程迁移任务最小区间的一个值
    private static final int MIN_TRANSFER_STRIDE = 16;
    
     * 扩容相关,计算扩容时生成的一个 表示戳
    private static int RESIZE_STAMP_BITS = 16;
    
     *  2^16-1=65535    并发扩容最多线程数
    private static final int MAX_RESIZERS = (1 << (32 - RESIZE_STAMP_BITS)) - 1;
    
     * 扩容相关
    private static final int RESIZE_STAMP_SHIFT = 32 - RESIZE_STAMP_BITS;
    
    // 当node节点的 hash值 为 -1 时,表示当前节点是FWD节点
    static final int MOVED     = -1; // hash for forwarding nodes
    // 当node节点的 hash值 为 -2 时,表示当前节点已经树化,且当前节点为TreeBin对象,TreeBin对象代理操作红黑树
    static final int TREEBIN   = -2; // hash for roots of trees
    static final int RESERVED  = -3; // hash for transient reservations
    // 二进制:31个 1    可以将一个负数通过位运算后 得到正数 但不是取绝对值
    static final int HASH_BITS = 0x7fffffff; // usable bits of normal node hash
     * 当前系统的CPU数量
    static final int NCPU = Runtime.getRuntime().availableProcessors();

LongAdder 变量:
不了解LongAdder,可以看这篇文章:

LongAdder源码解析

     * 散列表,长度一定是2次方数
    transient volatile Node<K,V>[] table;
    
     * 扩容过程中,会将扩容中的新table 赋值给 nextTable 保持引用,扩容结束之后,这里会被设置为Null
    private transient volatile Node<K,V>[] nextTable;
    
     * LongAdder 中的 baseCount 为发生竞争时, 或者 当前LongAdder 处于加锁状态时,增量累加到baseCount
    private transient volatile long baseCount;
    
     * LongAdder中的cellsBusy 0表示无锁,1表示加锁状态
    private transient volatile int cellsBusy;
    
    
     * LongAdder 中的cekks数组,当baseCount 发生竞争后,会创建cells数组
     * 线程会通过计算hash值 取道自己的cell,将增量累加到指定cell中
     * 总数 = sum(cells) + baseCount
    private transient volatile CounterCell[] counterCells;


     *    sizeCtl < 0
     * 1. -1 表示当前table正在初始化(有线程在创建table数组),当前线程需要自旋等待..
     * 2. 表示当前mao正在进行扩容 高16位表示:扩容的标识戳   低16位表示:(1 + nThread) 当前参与并发扩容的线程数量
     *
     *  sizeCtl = 0
     *  表示创建table数组时,使用 DEFAULT_CAPACITY 为大小
     *
     *  sizeCtl > 0
     *  1.如果table 未初始化,表示初始化大小
     *  2.如果已经初始化,表示下次扩容时的 触发条件(阈值)
    private transient volatile int sizeCtl;

     * 扩容过程中,记录当前进度,所有线程都需要从transferIndex 中分配区间任务,去执行自己的任务
    private transient volatile int transferIndex;


静态变量和静态代码块:

    // Unsafe mechanics
    private static final sun.misc.Unsafe U;
    /** 表示sizeCtl 属性在 ConcurrentHashMap 中内存偏移地址 */
    private static final long SIZECTL;
    /** 表示transferIndex 属性在ConcurrentHashMap 中内存偏移地址 */
    private static final long TRANSFERINDEX;
    /** 表示baseCount属性在ConcurrentHashMap 中内存偏移地址 */
    private static final long BASECOUNT;
    /** 表示cellsBusy属性在ConcurrentHashMap 中内存偏移地址 */
    private static final long CELLSBUSY;
    /** 表示cellValue属性在ConcurrentHashMap 中内存偏移地址 */
    private static final long CELLVALUE;
    /** 表示数组第一个元素的偏移地址 */
    private static final long ABASE;
    private static final int ASHIFT;

    static {
        try {
            U = sun.misc.Unsafe.getUnsafe();
            Class<?> k = ConcurrentHashMap.class;
            SIZECTL = U.objectFieldOffset
                (k.getDeclaredField("sizeCtl"));
            TRANSFERINDEX = U.objectFieldOffset
                (k.getDeclaredField("transferIndex"));
            BASECOUNT = U.objectFieldOffset
                (k.getDeclaredField("baseCount"));
            CELLSBUSY = U.objectFieldOffset
                (k.getDeclaredField("cellsBusy"));
            Class<?> ck = CounterCell.class;
            CELLVALUE = U.objectFieldOffset
                (ck.getDeclaredField("value"));
            Class<?> ak = Node[].class;
            ABASE = U.arrayBaseOffset(ak);
            // 表示数组单元 所占用空间大小,scale 表示Node[] 数组中 每一个单元所占用空间大小
            int scale = U.arrayIndexScale(ak);
            //如果不是2的次方数就报错 1 0000 & 0 1111 = 0
            if ((scale & (scale - 1)) != 0)
                throw new Error("data type scale not a power of two");
            //numberOfLeadingZeros() 返回当前数值的二进制,从高位到地位开始统计,看有多少个0 连续在一起
            // 4 => 100    numberOfLeadingZeros(4) = 32-3=29
            // ASHIFT = 31 - 29 = 2 ??
            // 拿下标为5的
            // ABASE + 5 * ASHIFT
            // ABASE + (5 << ASHIFT) = ABASE + 5*(1<<ASHIFT)   就是5*4
            ASHIFT = 31 - Integer.numberOfLeadingZeros(scale);
        } catch (Exception e) {
            throw new Error(e);
        }
    }

2、部分常见方法

1、Spread(int h)

     * 让高16位也参与运算
     *  1100 0011 1010 0101     0001 1100 0001 1110
     *  0000 0000 0000 0000     1100 0011 1010 0101
     *  ---- ---- ---- ----     ---- ---- ---- ---- ^
     *  1100 0011 1010 0101     1101 1111 1011 1011 = (h ^ (h >>> 16))
     *  0111 1111 1111 1111     1111 1111 1111 1111 = HASH_BITS
     *  ---- ---- ---- ----     ---- ---- ---- ---- &
     *  0100 0011 1010 0101     1101 1111 1011 1011
     */
    static final int spread(int h) {
        return (h ^ (h >>> 16)) & HASH_BITS;
    }

2、tabAt,casTabAt,setTabAt

    **
     *  scale代表一个单位长度 如:int = 4 则 scale = 4
     *  ASHIFT = 31 - Integer.numberOfLeadingZeros(scale);
     *  ASHIFT = 31 - (32 - 4) = 31 - 29 = 2
     *  相当于:
     *  i << ASHIFT = i * 4
     *  位操作 替代 乘法
     *
     *  ABASE:表示数组第一个元素的偏移地址
     */
    @SuppressWarnings("unchecked")
    static final <K,V> Node<K,V> tabAt(Node<K,V>[] tab, int i) {
        return (Node<K,V>)U.getObjectVolatile(tab, ((long)i << ASHIFT) + ABASE);
    }

    **@param tab   代表数组
     * @param i     代表位置
     * @param c     类似于期望值
     * @param v     要把 c 设置成v ,v为新值
     *  如果 (i << ASHIFT) + ABASE) ,该位置上的值和期望值 c 一样就替换成 v,返回true
     *
     */
    static final <K,V> boolean casTabAt(Node<K,V>[] tab, int i,
                                        Node<K,V> c, Node<K,V> v) {
        return U.compareAndSwapObject(tab, ((long)i << ASHIFT) + ABASE, c, v);
    }

    **@param tab   代表数组
     * @param i     代表位置
     * @param v     设置的值
     */
    static final <K,V> void setTabAt(Node<K,V>[] tab, int i, Node<K,V> v) {
        U.putObjectVolatile(tab, ((long)i << ASHIFT) + ABASE, v);
    }

3、resizeStamp 扩容版本戳

     * 16 -> 32
     * numberOfLeadingZeros(16=> 1 0000   = 32 - 5 = 27
     *                 0000 0000 0001 1011
     *              |  ---- ---- ---- ----
     * 1 << (16 - 1) = 1000 0000 0000 0000 => 32768
     *                 1000 0000 0001 1011 : 扩容版本戳
     * 扩容版本戳
     */
    static final int resizeStamp(int n) {
        return Integer.numberOfLeadingZeros(n) | (1 << (RESIZE_STAMP_BITS - 1));
    }

4、tableSizeFor

     * 返回比 c 大的 最小2的次方数
     * c = 6  返回 8
     * c = 9  返回 16
     */
    private static final int tableSizeFor(int c) {
        int n = c - 1;
        n |= n >>> 1;
        n |= n >>> 2;
        n |= n >>> 4;
        n |= n >>> 8;
        n |= n >>> 16;
        return (n < 0) ? 1 : (n >= MAXIMUM_CAPACITY) ? MAXIMUM_CAPACITY : n + 1;
    }

5、构造方法

     * 延迟创建 只有写入数据的时候才会创建 table
     */
    public ConcurrentHashMap() {
    }
    
     * 如果传入的值是16:
     * tableSizeFor(16 + 8 + 1 = 25) = 32
     * 
     * sizeCtl > 0
     *  当目前的table未初始化时,sizeCtl 表示初始化容量
     */
    public ConcurrentHashMap(int initialCapacity) {
        if (initialCapacity < 0)
            throw new IllegalArgumentException();
            
        int cap = ((initialCapacity >= (MAXIMUM_CAPACITY >>> 1)) ?
                   MAXIMUM_CAPACITY :
                   tableSizeFor(initialCapacity + (initialCapacity >>> 1) + 1));
        this.sizeCtl = cap;
    }

    public ConcurrentHashMap(Map<? extends K, ? extends V> m) {
        this.sizeCtl = DEFAULT_CAPACITY;
        putAll(m);
    }

     * initialCapacity:初始化容量
     * loadFactor:加载因子
     * concurrencyLevel:隔离级别
     *
     * 注:loadFactor 并没有设置到 LOAD_FACTOR (final 修饰)
     * private static final float LOAD_FACTOR = 0.75f
     *
     * 传入的两个参数 loadFactor 和 concurrencyLevel 只有计算初始化容量时有点作用,
     * 其它都没有用到,不会改变到默认的值
     */
    public ConcurrentHashMap(int initialCapacity,
                             float loadFactor, int concurrencyLevel) {
        if (!(loadFactor > 0.0f) || initialCapacity < 0 || concurrencyLevel <= 0)
            throw new IllegalArgumentException();
        if (initialCapacity < concurrencyLevel)   // Use at least as many bins
            initialCapacity = concurrencyLevel;   // as estimated threads
        /**  假如initialCapacity = 16 , loadFactor = 0.75
         *      size = 1 + 16 / 0.75 = 1 + 21.333 = 22.333
         *
         *      cap = 32  设置初始化容量 sizeCtl
         */
        long size = (long)(1.0 + (long)initialCapacity / loadFactor);
        int cap = (size >= (long)MAXIMUM_CAPACITY) ?
            MAXIMUM_CAPACITY : tableSizeFor((int)size);
        this.sizeCtl = cap;
    }
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值