Java程序员面试笔试真题与解析,拿捏了

    /* ---------------- Constants -------------- */

    /**
     * HashMap的最大容量
     */
    private static final int MAXIMUM_CAPACITY = 1 << 30;

    /**
     * 默认容量
     */
    private static final int DEFAULT_CAPACITY = 16;

    /**
     * 数组最大长度
     */
    static final int MAX_ARRAY_SIZE = Integer.MAX_VALUE - 8;

    /**
     * 默认最大并发等级
     */
    private static final int DEFAULT_CONCURRENCY_LEVEL = 16;

    /**
     * 负载因子
     */
    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;

    //最小转移步长
    private static final int MIN_TRANSFER_STRIDE = 16;

    //这个常量是用来计算HashMap不同容量有不同的resizeStamp用的
    private static int RESIZE_STAMP_BITS = 16;

    //最大参与扩容的线程数 相当大的一个数 基本上是不会触及该上线的
    private static final int MAX_RESIZERS = (1 << (32 - RESIZE_STAMP_BITS)) - 1;

    //要对resizeStamp进行位移运算的一个敞亮
    private static final int RESIZE_STAMP_SHIFT = 32 - RESIZE_STAMP_BITS;

    //特殊的节点哈希值
    static final int MOVED     = -1; // hash for forwarding nodes
    static final int TREEBIN   = -2; // hash for roots of trees
    static final int RESERVED  = -3; // hash for transient reservations
    static final int HASH_BITS = 0x7fffffff; // usable bits of normal node hash

    //获取CPU的数量
    static final int NCPU = Runtime.getRuntime().availableProcessors();

其中,因为JDK8中ConcurrentHashMap的实现方式和JDK7的不同,因此DEFAULT_CONCURRENCY_LEVEL已经没有实际作用了。并且在JDK8中,LOAD_FACTOR也已经固定成了0.75f。
另外,MOVED,TREEBIN,RESERVED是用来表示特殊节点的哈希值。该类特殊节点均不含实际元素,且其哈希值被设置为负数和普通节点区分。
剩下的涉及扩容的常量我们在相关的章节中再介绍。

成员变量

通常成员变量都是会负责记录当前类的状态的,ConcurrentHashMap也是如此。因此了解清除成员变量的作用,对我们后续分析ConcurrentHashMap的操作流程很有感帮助。

    /* ---------------- Fields -------------- */

    /**
     * 底层数组
     */
    transient volatile Node<K,V>[] table;
    //扩容时 使用的另一个数组
    private transient volatile Node<K,V>[] nextTable;

    //统计size的一部分
    private transient volatile long baseCount;

    /**
     * sizeCtl与table的resize和init有关
     * sizeCtl = -1时,表示table正在init
     * sizeCtl < 0 且不等于-1时,表示正在resize
     * sizeCtl > 0 时,表示下次需要resize的阈值,即capacity * loadfactory
     */
    private transient volatile int sizeCtl;

    //记录下一次要transfer对应的Index
    private transient volatile int transferIndex;

    //表示是否有线程正在修改CounterCells
    private transient volatile int cellsBusy;

    //用来统计size
    private transient volatile CounterCell[] counterCells;

    // views
    private transient KeySetView<K,V> keySet;
    private transient ValuesView<K,V> values;
    private transient EntrySetView<K,V> entrySet;

同样的,成员变量也可以按作用分成几类:

  • 用作底层数据结构的实现:Node<K,V>[] table
  • 用作统计元素的大小:baseCount、CounterCell[] counterCells、cellsBusy
  • 用作记录ConcurrentHashMap的状态:sizeCtl
  • 用作扩容记录:Node<K,V>[] nextTable、transferIndex
  • 用作转成其它类型的视图:KeySetView<K,V> keySet、ValuesView<K,V> values、EntrySetView<K,V> entrySet

table数组很好理解。因为HashMap的实现是基于数组的,在冲突时通过链地址解决。因此所有的数据都以数组为入口。
另一个数组nextTable是在扩容时备用的。 如果了解Redis的数据结构的读者,应该对这个不陌生,redis渐进式rehash就是通过两个哈希表加一个index实现的。而JDK8中在resize时,也采取了类似的方式(下文我们会介绍到:按步长逐步transfer)。

另外,比较重要的一个属性就是sizeCtl。
如果看过Doug Lea老爷子在JUC下的其他类,经常会有一个特殊的变量表示当前对象的状态。并且已CAS的方式去修改这个变量,实现自旋锁的功能(例如:AQS中的state)。
这里的sizeCtl就是一个富有特殊意义的变量。
当sizeCtl大于0时,表示扩容的阈值(没错,就是HashMap中threshold变量的作用),而且上文我们也了解到在JDK8中由于loadfactor已经被固定为0.75f。因此在正常状态下(非扩容状态),sizeCtl = oldCap >> 1 - (oldCap << 2)。 而sizeCtl == -1是一个特殊的状态标志,表示ConcurrentHashMap正在初始化底层数组。
当sizeCtl为其他负数时,表示ConcurrentHashMap正在进程扩容,其中,高16位可以反应出扩容前数组的大小,而后16位可以反应出此时参与扩容的线程数。

内部类

ConcurrentHashMap拥有大量的内部类,但其中大部分都是用来遍历或是在Fork/Join框架中平行遍历时使用的。这部分类内部类我们不在过多介绍。主要看CountCell和几个Node的类。

CounterCell

首先,CounterCell是用来统计ConcurrentHashMap用的,其内部有个v

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值