ConcurrentHashMap解析一

首先介绍一下ConcurrentHashMap的成员变量和常量

Constants

    private static final int MAXIMUM_CAPACITY = 1 << 30; //最大容量2的30次方
    private static final int DEFAULT_CAPACITY = 16; //默认容量  1<<4
    
    private static final float LOAD_FACTOR = 0.75f;  //负载因子
    static final int TREEIFY_THRESHOLD = 8;  //链表转为红黑树,大于8小于6先对链表数组进行翻倍扩容操作
    static final int UNTREEIFY_THRESHOLD = 6;  //树转列表
    static final int MIN_TREEIFY_CAPACITY = 64; //链表真正转为红黑树
    private static final int MIN_TRANSFER_STRIDE = 16;
    private static int RESIZE_STAMP_BITS = 16;//stamp高位标识移动位数
    private static final int MAX_RESIZERS = (1 << (32 - RESIZE_STAMP_BITS)) - 1;
    private static final int RESIZE_STAMP_SHIFT = 32 - RESIZE_STAMP_BITS;
    static final int MOVED     = -1; // forwarding nodes 的hash值,如果hash值等于-1代表线程协助扩容
    static final int TREEBIN   = -2; // roots of trees 的hash值,如果hash等于-2代表,当前桶是红黑树
    static final int RESERVED  = -3; // transient reservations 的hash值

    // usable bits of normal node hash,在hash计算的时候运用到,与HashMap计算出来的hash值进行与操作
    static final int HASH_BITS = 0x7fffffff; 
    
    static final int NCPU = Runtime.getRuntime().availableProcessors(); //可用处理器数量       

Fields

transient volatile Node<K,V>[] table;//当前ConcurrentHashmap的Node数组

private transient volatile Node<K,V>[] nextTable;//ForwardNode所指向的下一个表


private transient volatile long baseCount;//如果使用CAS计数成功,使用该值进行累加

//扩容设置的参数,默认为0,当值=-1的时候,代表当前有线程正在进行扩容操作
//当值等于-n的时候,代表有n个线程一起扩容,其中n-1线程是协助扩容
//当在初始化的时候指定了大小,这会将这个大小保存在sizeCtl中,大小为数组的0.75
private transient volatile int sizeCtl;


private transient volatile int transferIndex;


private transient volatile int cellsBusy;

//如果使用CAS计算失败,也就是说当前处于高并发的情况下,那么
//就会使用CounterCell[]数组进行计数,类似jdk1.7分段锁的形式,锁住一个segment
//最后size()方法统计出来的大小是baseCount和counterCells数组的总和
private transient volatile CounterCell[] counterCells;

然后我们介绍一下ConcurrentHashMap为我们提供的三个核心的原子方法:

     //获取当前地址偏移量的Node对象
    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);
    }
//对比当前地址偏移量的对象和第三个参数的对象是否为同一个对象
    //一样则将这个对象替换成第四个参数的对象并且返回true
    //不一样则直接返回一个false
    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);
    }
//将当前地址偏移量的对象设置成第三个参数的对象
    static final <K,V> void setTabAt(Node<K,V>[] tab, int i, Node<K,V> v) {
        U.putObjectVolatile(tab, ((long)i << ASHIFT) + ABASE, v);
    }

三个方法的作用都在代码上注释了,三个方法中调用的方法都是native类型的无法直接看到源码,那么我们分析一下,他是怎么获取到地址偏移量的,在代码中我们看到了他进行了这样的转换
((long)i << ASHIFT) + ABASE
这条代码中有两个变量,我解释一些这两个变量
ABASE:是针对当前数组首个元素的地址偏移量
ASHIFT:是针对当前数组某个位置上元素所占字节并且经过相对应的处理而产生的结果,下面会重点讲解一下这个

首先是ABASE变量的获取:
ABASE = U.arrayBaseOffset(对象);
U是Unsafe的实例对象,这个对象不能轻易获取到,不过可以通过反射来获取,示例:

    try {
        Field f = Unsafe.class.getDeclaredField("theUnsafe");
        f.setAccessible(true);
        U = (Unsafe) f.get(null);
        int scare = U.arrayIndexScale(String[].class);
        System.out.println(scare);
    } catch (Exception e) {
        e.printStackTrace();
    }

在ConcurrentHashMap中,传入这个方法的对象其实不是对象本身,而是对象的一个引用,所以我们来看下ASHIFT变量获取的方法
首先通过

      int scale = U.arrayIndexScale(ak);

来进行获取当前数组元素所占字节,因为是对象引用,所以都是返回4
然后再进行第二步的处理:

    ASHIFT = 31 - Integer.numberOfLeadingZeros(scale);

这个地方才是真的为ASHIFT进行赋值的地方,后一个方法返回的是参数的二进制代码左侧包含0的个数,在当前实例中,参数为4,二进制表示为100,前面含有29个零,所以这个变量被赋值为2
这样我们就可以总结一下,地址偏移量 = (传入值 << ASHIFT) + ABASE
这三个方法频繁的在CAS算法中使用,所以要先了解一下。

下面分析一下ConcurrentHashMap的构造方法,这个类有五个构造方法分别是
1、public ConcurrentHashMap()//无参构造方法
2、public ConcurrentHashMap(int initialCapacity)//参数为容器容量
3、public ConcurrentHashMap(Map<? extends K, ? extends V> m)//参数为传入的map集合
4、public ConcurrentHashMap(int initialCapacity, float loadFactor)//参数1容量,参数2负载因子
5、public ConcurrentHashMap(int initialCapacity,float loadFactor, int concurrencyLevel)
//第一个参数容量,第二个参数负载因子,第三个参数并发级别,用来确定segment的

这里我主要拿一个构造方法出来分析:

    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;

    }

这个构造方法,我们可以看出,如果你传入的参数是容量大小,那么他在进行向上找2倍数最近值的时候,他传入tableSizeFor()方法的参数是之前传入容量的1.5倍+1,这个地方在后头链表超过长度8开始转换的时候也有同样的操作,也就是说,扩容是按照两倍来扩容的,最后将获得的值cap赋值给sizeCTL,这边也进一步说明了,sizeCTL为正代表的是容量的大小。

参考链接:
https://blog.csdn.net/u012129558/article/details/52268194
https://blog.csdn.net/u010723709/article/details/48007881
https://www.jianshu.com/p/9819eb48716a
https://www.jianshu.com/p/c17ca559886d

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 218,451评论 6 506
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 93,172评论 3 394
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 164,782评论 0 354
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 58,709评论 1 294
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 67,733评论 6 392
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 51,578评论 1 305
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 40,320评论 3 418
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 39,241评论 0 276
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 45,686评论 1 314
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,878评论 3 336
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,992评论 1 348
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,715评论 5 346
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 41,336评论 3 330
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,912评论 0 22
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 33,040评论 1 270
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 48,173评论 3 370
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,947评论 2 355

推荐阅读更多精彩内容

  • 一、基本数据类型 注释 单行注释:// 区域注释:/* */ 文档注释:/** */ 数值 对于byte类型而言...
    龙猫小爷阅读 4,261评论 0 16
  • 在一个方法内部定义的变量都存储在栈中,当这个函数运行结束后,其对应的栈就会被回收,此时,在其方法体中定义的变量将不...
    Y了个J阅读 4,418评论 1 14
  • Spring Cloud为开发人员提供了快速构建分布式系统中一些常见模式的工具(例如配置管理,服务发现,断路器,智...
    卡卡罗2017阅读 134,657评论 18 139
  • 在这里,我们是最伤感的孩子, 等秋风,等你 ,以及金色的灰尘。 我们,来自最久远的春天, 最安静的田园,来自 一场...
    forgetmefish阅读 673评论 9 18
  • 吃完了泡面,碗还在桌子上放着,水杯里泡了一天的菊花在杯底团成一坨,盘子里堆了一半瓜子和一半瓜子皮,我仰在床上,刷着...
    阳小傻阅读 258评论 2 4