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);
      }
      

補充

五、總結

發佈了48 篇原創文章 · 獲贊 17 · 訪問量 5萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章