Striped64

Striped64阅读笔记

一、简介

Striped64是在java8中添加用来支持累加器的并发组件,它可以在并发环境下使用来做某种计数,Striped64的设计思路是在竞争激烈的时候尽量分散竞争,在实现上,Striped64维护了一个base Count和一个Cell数组,计数线程会首先试图更新base变量,如果成功则退出计数,否则会认为当前竞争是很激烈的,那么就会通过Cell数组来分散计数,Striped64根据线程来计算哈希,然后将不同的线程分散到不同的Cell数组的index上,然后这个线程的计数内容就会保存在该Cell的位置上面,基于这种设计,最后的总计数需要结合base以及散落在Cell数组中的计数内容。这种设计思路类似于java7的ConcurrentHashMap实现,也就是所谓的分段锁算法,ConcurrentHashMap会将记录根据key的hashCode来分散到不同的segment上,线程想要操作某个记录只需要锁住这个记录对应着的segment就可以了,而其他segment并不会被锁住,其他线程任然可以去操作其他的segment,这样就显著提高了并发度,虽然如此,java8中的ConcurrentHashMap实现已经抛弃了java7中分段锁的设计,而采用更为轻量级的CAS来协调并发,效率更佳。

二、继承关系图

继承了抽象Number类

三、存储结构

使用内部类Cell,内部的函数和属性的修饰的访问权限都是default,只能包内的对象访问

四、源码分析

内部类
//它是Striped64实现分散计数的最为基础的数据结构
@sun.misc.Contended static final class Cell {
    volatile long value;
    Cell(long x) { value = x; }
    final boolean cas(long cmp, long val) {
        //调用unsafe.compareAndSwapLoing进行元素值替换
        return UNSAFE.compareAndSwapLong(this, valueOffset, cmp, val);
    }

    // Unsafe mechanics 
    private static final sun.misc.Unsafe UNSAFE;
    private static final long valueOffset;
    static {
        try {
            //初始化Unsafe和valueOffset值
            UNSAFE = sun.misc.Unsafe.getUnsafe();
            Class<?> ak = Cell.class;
            valueOffset = UNSAFE.objectFieldOffset
                (ak.getDeclaredField("value"));
        } catch (Exception e) {
            throw new Error(e);
        }
    }
}
/*
value:cell的元素值
valueOffset:value在cell对象中的内存偏移量
UNSAFE:工具类
*/
属性
transient volatile Cell[] cells;//存储各阶段的值,
transient volatile long base;//最初无竞争时使用,也是一种特殊的段
transient volatile int cellsBusy;//标记当前是否有线程在创建cells、扩容cells、创建cell,通过cas更新该值,相当于一个锁
static final int NCPU = Runtime.getRuntime().availableProcessors(); //可用处理器数量,获取系统的可用进程来限制表的大小

//Unsafe mechanics unsafe和属性基础对象的偏移量
private static final sun.misc.Unsafe UNSAFE;
private static final long BASE;//base值在对象的内存偏移量
private static final long CELLSBUSY;//cellsBusy值在对象中的内存偏移量
private static final long PROBE;//threadLocalRandomProbe属性在Thread类中的内存偏移量
static {
    try {
        //初始化属性基础对象的内存偏移量
        UNSAFE = sun.misc.Unsafe.getUnsafe();
        Class<?> sk = Striped64.class;
        BASE = UNSAFE.objectFieldOffset
            (sk.getDeclaredField("base"));
        CELLSBUSY = UNSAFE.objectFieldOffset
            (sk.getDeclaredField("cellsBusy"));
        Class<?> tk = Thread.class;
        PROBE = UNSAFE.objectFieldOffset
            (tk.getDeclaredField("threadLocalRandomProbe"));
    } catch (Exception e) {
        throw new Error(e);
    }
}
构造
  • Striped64() {}
主要方法
  • longAccumulate:针对long类型实现计数

    • /**
      * Handles cases of updates involving initialization, resizing,
      * creating new Cells, and/or contention. See above for
      * explanation. This method suffers the usual non-modularity
      * problems of optimistic retry code, relying on rechecked sets of
      * reads.
      *
      * @param x the value
      * @param fn the update function, or null for add (this convention
      * avoids the need for an extra field or function in LongAdder).
      * @param wasUncontended false if CAS failed before call
      */
      final void longAccumulate(long x, LongBinaryOperator fn,
                                boolean wasUncontended) {
          int h;
          if ((h = getProbe()) == 0) {//代表probe没有初始化
              ThreadLocalRandom.current(); // force initialization
              h = getProbe();//重新获取probe
              wasUncontended = true;//没有初始化,所以不存在锁竞争
          }
          //是否发生碰撞,或者其他冲突
          boolean collide = false;                // True if last slot nonempty
          for (;;) {
              //as 获取ceels数组表
              //a 获取当前线程cell,通过as[(n - 1) & h]
              //n 获取ceels数组大小
              Cell[] as; Cell a; int n; long v;
              //一、判断cells是否为null,且长度是否大于0
              if ((as = cells) != null && (n = as.length) > 0) {
                  //1、如果取出的ceel == null 说明此锁未被占用
                  if ((a = as[(n - 1) & h]) == null) {
                      //如果不在扩容和新建状态
                      if (cellsBusy == 0) {       // Try to attach new Cell
                          Cell r = new Cell(x);   // Optimistically create
                          //进行锁占用
                          if (cellsBusy == 0 && casCellsBusy()) {
                              boolean created = false;//默认创建失败
                              try {               // Recheck under lock
                                  Cell[] rs; int m, j;
                                  //再验证表是否为null和当前是否被占用
                                  if ((rs = cells) != null &&
                                      (m = rs.length) > 0 &&
                                      rs[j = (m - 1) & h] == null) {
                                      //更新
                                      rs[j] = r;
                                      created = true;//标识此次创建成功
                                  }
                              } finally {
                                  cellsBusy = 0;//释放当前锁
                              }
                              if (created)//创建成功,退出
                                  break;
                              continue; //创建失败,不修改probe,重试 // Slot is now non-empty
                          }
                      }
                      //标记当前没有出现碰撞
                      collide = false;
                  }
                  //2、当前线程不为null,且更新失败了
                  //   这里就是简单的自旋一次,然后下面修改probe然后重试
                  else if (!wasUncontended)       // CAS already known to fail
                      wasUncontended = true;      // Continue after rehash
                  //3、尝试cas更新一次,成功返回
                  else if (a.cas(v = a.value, ((fn == null) ? v + x :
                                               fn.applyAsLong(v, x))))
                      break;
                  //4、设置的cells数组的长度达到了cpu的核心数,或者cells扩容了
                  else if (n >= NCPU || cells != as)
                      collide = false;            // At max size or stale
                  //5、 第 3 都更新失败,且第 4 也不成立,说明出现冲突了,就简单自选一次,重新开始
                  else if (!collide)
                      collide = true;
                 	//6、明显出现冲突了,占有锁,并进行扩容
                  else if (cellsBusy == 0 && casCellsBusy()) {
                      try {
                          if (cells == as) {      // Expand table unless stale
                              Cell[] rs = new Cell[n << 1];
                              for (int i = 0; i < n; ++i)
                                  rs[i] = as[i];
                              cells = rs;
                          }
                      } finally {
                          cellsBusy = 0;//解锁
                      }
                      collide = false;//标记没有出现碰撞
                      continue;   //扩容后,不修改probe,进行重试   // Retry with expanded table
                  }
                  //recall:更新失败,或达到CPU核心数,重新生成probe,并且重试
                  h = advanceProbe(h);
              } 
              //二、就判断cellsbusy是否为0 (是否为无锁状态)
              //		cells是否为null 
              //		修改cellsbusy为1(尝试加锁,加锁成功true,否则false)
              else if (cellsBusy == 0 && cells == as && casCellsBusy()) {
                  boolean init = false;//初始化默认不成功
                  try {                           // Initialize table
                      if (cells == as) {
                          Cell[] rs = new Cell[2];//新建一个大小为2的cell数组
                          rs[h & 1] = new Cell(x);//找到当前线程的hash在数组中对应的cell
                          cells = rs;//把初始化的表赋值给cells属性
                          init = true;//初始化成功
                      }
                  } finally {
                      cellsBusy = 0;//初始化完成就修改cellsbusy为0(也是释放锁)
                  }
                  if (init)//初始化成功则终止cas
                      break;
              }
              //三、如果与其他线程在初始化cells数组中,就尝试更新base
              //		如果更新成功则返回
              else if (casBase(v = base, ((fn == null) ? v + x :
                                          fn.applyAsLong(v, x))))
                  break;                          // Fall back on using base
          }
      }
      
  • doubleAccumulate:针对double类型实现计数

    • 源码类似long
其他方法
  • getProbe:获得Thread.currentThread()中的threadLocalRandomProbe值,用于重新随机获取probe值

    • /**
       * Returns the probe value for the current thread.
       * Duplicated from ThreadLocalRandom because of packaging restrictions.
       */
      static final int getProbe() {
          return UNSAFE.getInt(Thread.currentThread(), PROBE);
      }
      
  • advanceProbe:修改thread中threadLocalRandomProbe的值为推进后的probe,并返回新的probe

    • /**
       * Pseudo-randomly advances and records the given probe value for the
       * given thread.
       * Duplicated from ThreadLocalRandom because of packaging restrictions.
       */
      static final int advanceProbe(int probe) {
          probe ^= probe << 13;   // xorshift
          probe ^= probe >>> 17;
          probe ^= probe << 5;
          UNSAFE.putInt(Thread.currentThread(), PROBE, probe);
          return probe;
      }
      
  • casBase:cas替换base属性

    • //CASes the base field.
      final boolean casBase(long cmp, long val) {
          return UNSAFE.compareAndSwapLong(this, BASE, cmp, val);
      }
      
  • casCellsBusy:如果cellsbusy为0则cas替换为1

    • //CASes the cellsBusy field from 0 to 1 to acquire lock.
      final boolean casCellsBusy() {
          //如果cellsbusy属性是0,则修改为1,且返回true。否则返回false
          return UNSAFE.compareAndSwapInt(this, CELLSBUSY, 0, 1);
      }
      
补充

五、总结

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

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值