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); }
-
補充
無