onlyit onlyifabsent( 二 )

  • CAS 操作是需要知道目标对象在内存中的地址的,上面通过 CAS 对数组目标索引位进行赋值的操作 tabAt(tab, i = (n - 1) & hash),因为 table 这个对象类里面是有变量保存的,而数组又是连续的内存地址,所以通过索引是可以得知相应的内存地址的,而对节点的 val 进行 CAS 操作是需要知道节点的 val 变量内存地址的,而这个地方没办法得知,所以也就无法进行 CAS 操作
  • transient volatile Node<K,V>[] table;
    • 当链表中所有的 key 都不符合要求时,会创建一个新的节点挂在最后的位置上,而之前最后一个节点的下一个节点位置是不知道的,所以也没法进行 CAS 操作
    我们继续往下看,加锁之后立马进行了一次 if 判断,这个判断很容易被忽略,但是又是必不可少的
    onlyit onlyifabsent

    文章插图

    比如在上一个 if 判断执行后,加锁之前,这个 f 节点刚好被扩容线程操作过,那么此时的 tabAt(tab, i) != f,细节,细节!
    接下来就是判断是链表还是红黑树,红黑树只是个算法工具的应用,本文不做说明
    我们接着看链表,首先会判断头节点是否符合要求,符合要求就把值赋给oldVal,onlyIfAbsent 这个变量的意思是,是否仅当不存在的时候赋值,如果为 true,那么就是 val 为 null 就赋值,不为 null,不赋值,只返回原值,这里是 false,所以会覆盖原值 。
    如果头节点不符合,接着往下遍历,如果整个链表遍历完都不符合,那么在链表的结尾插入构造的新的 Node 节点,在锁结构体执行完会进行一个链表转红黑树的判断,链表转红黑树阈值是 8,红黑树转链表阈值是 6,之所以不是同一个值就是不想 2 种结构频繁的互相转换
    if (binCount != 0) {if (binCount >= TREEIFY_THRESHOLD)treeifyBin(tab, i);if (oldVal != null)return oldVal;break;}最后如果存在符合条件的 key,就返回原值,不存在就跳出循环,执行 putVal 方法的最后一个逻辑 addCount 方法,这个方法只有当整个集合中不存在符合条件的 key 时才会执行到,否则就返回原值,提前 return 了,下面我们来看 addCount 方法 。
    addCount
    onlyit onlyifabsent

    文章插图

    addCount 主要分为 2 大块,第一个 if 是计算集合的容量,第二个 if 是判断是否需要扩容,在看之前,读者不妨思考一下这个问题,在高并发的情况下,怎样高效的计算集合的容量呢?
    Doug 老爷子在这里给出了答案,通过分治的思想高效计算容量,怎样实现的呢
    private transient volatile long baseCount;private transient volatile CounterCell[] counterCells;Doug 老爷子设置了一个 long 型变量 baseCount,一个 CounterCell 类型的数组,CounterCell 实际上内部只有一个 long 型变量 。
    @sun.misc.Contended static final class CounterCell {volatile long value;CounterCell(long x) { value = https://www.baikexueshe.com/s/x; }}读者有没有注意到,这个类使用了一个@Contended 注解,这个注解是不是很陌生呀,他的作用是防止伪共享,这个展开来又是一篇文章了,有兴趣的读者可以去研究一下
    所以,这里计算容量的总体思想就是首先尝试对 baseCount 进行+1,如果失败了,随机获取 CounterCell 数组中的一个下标位,对他尝试进行+1,这样就将对容量的并发操作分开来了,大大提高了并发性能,理想情况下最多支持 CPU 个数的线程同时对容量进行操作,学到了没
    onlyit onlyifabsent


    特别声明:本站内容均来自网友提供或互联网,仅供参考,请勿用于商业和其他非法用途。如果侵犯了您的权益请与我们联系,我们将在24小时内删除。