onlyit onlyifabsent( 五 )


fullAddCount这个方法有点长,我们分 4 部分来说明,第一部分
if ((h = ThreadLocalRandom.getProbe()) == 0) {ThreadLocalRandom.localInit();// force initializationh = ThreadLocalRandom.getProbe();wasUncontended = true;}刚进来就会判断 ThreadLocalRandom.getProbe()是否等于 0,前文说过,这个值是线程的唯一身份码,如果为 0 就进行初始化
static final void localInit() {int p = probeGenerator.addAndGet(PROBE_INCREMENT);int probe = (p == 0) ? 1 : p; // skip 0long seed = mix64(seeder.getAndAdd(SEEDER_INCREMENT));Thread t = Thread.currentThread();UNSAFE.putLong(t, SEED, seed);UNSAFE.putInt(t, PROBE, probe);}从源码中可以看到,初始化之后的值是不可能为 0的,如果为 0 会被改成 1,为什么不能为 0 呢,这个稍后会提到
接下来会进入一个死循环,循环中主要分 3 大块,简写一下

  • if ((as = counterCells) != null && (n = as.length) > 0)
  • else if (cellsBusy == 0 && counterCells == as && U.compareAndSwapInt(this, CELLSBUSY, 0, 1))
  • else if (U.compareAndSwapLong(this, BASECOUNT, v = baseCount, v + x))
先判断 counterCells 数组是否为空,为空进入第二个 if,尝试初始化
else if (cellsBusy == 0 && counterCells == as && U.compareAndSwapInt(this, CELLSBUSY, 0, 1)) {boolean init = false;try {if (counterCells == as) {CounterCell[] rs = new CounterCell[2];rs[h & 1] = new CounterCell(x);counterCells = rs;init = true;}} finally {cellsBusy = 0;}if (init)break;}这块代码比较简单,初始化一个容量为 2 的数组,然后新建一个 CounterCell 对象赋值给目标索引位,初始化完毕退出循环
else if (U.compareAndSwapLong(this, BASECOUNT, v = baseCount, v + x))break;如果 CounterCell 数组为空,初始化又尝试失败了,这个时候会再一次尝试对 baseCount 进行 CAS,万一成功了呢
接下来,我们重点看下第一个 if,里面有好几处细节
onlyit onlyifabsent

文章插图

里面分成了 6 个判断,① 判断目标索引位是否为 null,为 null 尝试 CAS 将 cellsBusy 设为 1
private transient volatile int cellsBusy;这个变量同样是被 volatile 修饰的,用来控制对 CounterCell 数组的操作,看到这里,读者可能又有疑问了,哈哈,疑问好多
为什么这里连续 2 次判断cellsBusy == 0
onlyit onlyifabsent

文章插图

这里只能说 Doug 老爷子的"毛病"又犯了,注意我这里加了引号哈
怎么说呢
因为这里会进行一个 CAS 操作,CAS 是什么,也是一种锁,既然涉及到锁,那么就会存在锁区间,我们都知道,锁区间越小越好,这样才会将性能的损耗降到最低,Doug 老爷子当然也是知道的,而这里会有一个新建对象的操作,新建对象会发生什么呢
  • 检查常量池是否有类的符号引用
  • 是否已被加载解析初始化
  • 分配内存
  • 初始化零值
  • 设置对象头信息
  • 引用赋值
而分配内存的时候,其实也是会存在并发竞争的,所以 JVM 采用 2 种机制
  • CAS+失败重试
  • TLAB 线程本地分配缓冲区
现在是不是觉得一个 new 看起来很简单,里面道道还挺多的,所以老爷子将新建对象的操作抽离了出来,目的就是为了减少锁区间,反正就算后面的 CAS 失败了,重新再来一轮就是了,反正 java 有 GC 机制(手动滑稽),而且可以看到锁区间里的操作非常简单,就是一个赋值操作,执行非常快,佩服,佩服


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