LongAdder原理及創(chuàng)建使用示例詳解
LongAdder介紹
1.Atomic原子類
Atomic的原子類內部使用的是CAS原則,CAS是一個樂觀鎖,但是如果是在高并發(fā)的情況下的話,多個線程不斷地競爭
CAS的不斷的自旋,非常耗CPU.
高并發(fā)環(huán)境下,value變量其實是一個熱點,也就是多個線程競爭一個熱點。
這時候就需要使用的 LongAdder 來替代 Atomic類
2.LongAdder原理
LongAdder的原理就是分散熱點,將value分散到一個數(shù)組中,不同的線程去找自己的對應的Cell進行修改值,
各個線程對Cell進行CAS操作,這樣熱點就被分散了,沖突的概率小了,性能就提高了.
如果要返回實際的值,返回所有的數(shù)組中的值和base值就行.

2.查看LongAdder的add方法
public void add(long x) {
Cell[] as; long b, v; int m; Cell a;
if ((as = cells) != null || !casBase(b = base, b + x)) {
boolean uncontended = true;
if (as == null || (m = as.length - 1) < 0 ||
(a = as[getProbe() & m]) == null ||
!(uncontended = a.cas(v = a.value, v + x)))
longAccumulate(x, null, uncontended);
}
}
一看到這種代碼就頭皮發(fā)麻
簡單說明一下這幾行代碼作用,說多了,你也懶得看
第一個if作用:
如果還沒有初始化cells數(shù)組,就去修改base值
如果修改Base值失敗,說明多個線程對base值修改發(fā)生了競爭
第二個if作用:
判斷有沒有cells有沒有值,有的話說明已經(jīng)初始化過cells數(shù)組了
知道對應的桶位添加值或者修改值
我感覺你已經(jīng)不知道我在說什么了!
反正你就知道,如果有多個線程發(fā)生了競爭,就去cells數(shù)組中找對應的桶位的cell添加或者修改值即可
3.longAccumulate方法
這里的代碼特別的惡心,是能助眠的好代碼,建議收藏!
簡單說明一下幾個核心的代碼
3.1.創(chuàng)建Cell數(shù)組
else if (cellsBusy == 0 && cells == as && casCellsBusy()) {
boolean init = false;
try {
if (cells == as) {
Cell[] rs = new Cell[2];
rs[h & 1] = new Cell(x);
cells = rs;
init = true;
}
} finally {
cellsBusy = 0;
}
if (init)
break;
}
Cell數(shù)組還沒有進行初始化,
創(chuàng)建長度為2的Cell數(shù)組
在索引1的位置存儲第一個Cell對象
3.2.創(chuàng)建桶位Cell對象
if ((as = cells) != null && (n = as.length) > 0) {
if ((a = as[(n - 1) & h]) == null) {
if (cellsBusy == 0) {
Cell r = new Cell(x);
if (cellsBusy == 0 && casCellsBusy()) {
boolean created = false;
try {
Cell[] rs; int m, j;
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;
}
}
m - 1) & h 通過路由尋址公式找到對應的索引位置的桶位為空,然后把創(chuàng)建的Cell對象存儲進去
更直白就是說,你要蹲坑了,你要去找坑,如果坑位沒有人,你就進去
3.3.擴容Cell數(shù)組
else if (cellsBusy == 0 && casCellsBusy()) {
try {
if (cells == as) {
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;
}
數(shù)組長度不夠時,2倍擴容
4.總結
你現(xiàn)在肯定是云里霧里,講的都是什么東西.
你現(xiàn)在就只需要知道 LongAdder
通過 base 和 Cell數(shù)組 換取更高的性能
5.附上源碼解析
當然這里你可以跳過了,不用看了
5.1.add方法
public void add(long x) {
// as 表示cells引用
// b 表示獲取的base值
// v 表示期望值
// m 表示cells數(shù)組的長度
// a 表示當前線程命中cell單元格
Cell[] as; long b, v; int m; Cell a;
//條件一: --> true 表示cells已經(jīng)初始化過了,當前線程應該把數(shù)據(jù)寫到對應的cell中
// --> false 表示cells還沒有初始化過,當前線程所有的數(shù)據(jù)都被寫到base中
//條件二: --> false 表示cas替換值成功
// --> true 表示發(fā)生競爭了,可能需要重試 或者 擴容
if ((as = cells) != null || !casBase(b = base, b + x)) {
//什么時候會進來?
//1.true 表示cells已經(jīng)初始化過了,當前線程應該把數(shù)據(jù)寫到對應的cell中
//2.true cells還沒有初始化,但是發(fā)生競爭了
// true -> 未競爭 false ->發(fā)生競爭
boolean uncontended = true;
//as == null || (m = as.length - 1) < 0 看做是一個條件
//條件一:true --> 說明cells還沒有被初始化,也就是多線程寫base發(fā)生競爭了進來的,也就是通過第二個條件進來的
// false --> 說明cells已經(jīng)初始化了,可以去尋找自己的cell進行賦值
//條件二: getProbe() 可以理解為獲取當前線程的hash值, m表示cells長度-1 注意:cells的次方數(shù)一定是2的次方
// true --> 說明當前線程對應的下標的cell為空,需要創(chuàng)建 longAccumulate 支持
// false --> 說明當前線程對應的下標cell不為空,下一步需要將 x,添加到cell中
//條件三: true --> 代表cas失敗,代表當前線程對應的cell,有競爭
// false --> 表示cas成功
if (as == null || (m = as.length - 1) < 0 ||
(a = as[getProbe() & m]) == null ||
!(uncontended = a.cas(v = a.value, v + x)))
//都哪些情況會調用這里的方法?
//1.true --> 說明cells還沒有被初始化,也就是多線程寫base發(fā)生競爭了進來的
//2.true --> 說明當前線程對應的下標的cell為空,需要創(chuàng)建 longAccumulate 支持
//3.true --> 代表cas失敗,代表當前線程對應的cell,有競爭
longAccumulate(x, null, uncontended);
}
}
5.2.longAccumulate方法
/**
* //1.true --> 說明cells還沒有被初始化,也就是多線程寫base發(fā)生競爭了進來的,到時候會進行初始化cell
* //2.true --> 說明當前線程對應的下標的cell為空,需要創(chuàng)建 longAccumulate 支持
* //3.true --> 對應的下標也有cell,但是cas失敗,代表當前線程對應的cell,有競爭
*/
//wasUncontended,只有cells初始化之后,并且當前線程 競爭修改失敗,才會是false,其他情況下都是true
final void longAccumulate(long x, LongBinaryOperator fn,
boolean wasUncontended) {
//表示線程的hash值
int h;
//條件成立,說明當前線程還未分配hash值
if ((h = getProbe()) == 0) {
//給當前線程分配hash值
ThreadLocalRandom.current(); // force initialization
//取出當前線程的hash值賦值給h
h = getProbe();
//為什么?默認情況下,可定寫入到了cell[0]的位置,不把他當做一次真正的競爭??
wasUncontended = true;
}
//表示擴容意向, false表示不會擴容 , true有可能會擴容
boolean collide = false; // True if last slot nonempty
//自旋
for (;;) {
//as 表示cells引用
//a 表示當前線程命中的cell
//n 表示cell的數(shù)組長度
//v 表示期望值
Cell[] as; Cell a; int n; long v;
//情況1:as不為空表示cells已經(jīng)初始化了,當前線程應該將數(shù)據(jù)寫入到對應的cell中
if ((as = cells) != null && (n = as.length) > 0) {
//2.true --> 說明當前線程對應的下標的cell為空,需要創(chuàng)建 longAccumulate 支持
//3.true --> 代表cas失敗,代表當前線程對應的cell,有競爭[重試或者擴容]
//情況1.1: true 表示當前線程對應的下標位置的cell為null,需要創(chuàng)建cell
if ((a = as[(n - 1) & h]) == null) {
//true 表示當前鎖未被占用,false-->表示鎖被占用
if (cellsBusy == 0) { // Try to attach new Cell
//拿x創(chuàng)建cell
Cell r = new Cell(x); // Optimistically create
//條件一:true 表示當前鎖未被占用,false-->表示鎖被占用
//條件二:true表示當前線程獲取鎖成功,false表示當前線程獲取鎖失敗,
if (cellsBusy == 0 && casCellsBusy()) {
//是否創(chuàng)建成功的標記
boolean created = false;
try { // Recheck under lock
//rs表示cells引用
//m 表示cells長度
//j 表示當前線程命中的下標
Cell[] rs; int m, j;
//條件一,條件二,恒成立的
//rs[j = (m - 1) & h] == null 為了防止其他線程已經(jīng)初始化話過了,防止當前線程再次修改,覆蓋掉原來的cell
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; // Slot is now non-empty
}
}
//擴容意向改為false
collide = false;
}
//情況1.2
//wasUncontended,只有cells初始化之后,并且當前線程 競爭修改失敗,才會是false,其他情況下都是true
else if (!wasUncontended) // CAS already known to fail
wasUncontended = true; // Continue after rehash
//情況1.3 :當前線程rehash過后,然后新命中的cell不為空
//true --> 寫成功 退出
//false -- > rehash過后命中的cell,也有競爭 這里為重試一次,再重試一次
else if (a.cas(v = a.value, ((fn == null) ? v + x :
fn.applyAsLong(v, x))))
break;
//情況1.4.
// 條件一: n >= NCPU true --> 擴容意向改為False ,表示不擴容了 false --> 表示cells還可以擴容
//條件二: true --> 表示其他線程已經(jīng)擴容過了,當前線程rehash之后重試即可
else if (n >= NCPU || cells != as)
collide = false; // At max size or stale
//默認開始為false 的, 設置為true,需要擴容,但是不一定真的發(fā)生擴容
else if (!collide)
collide = true;
//情況6.真正的擴容邏輯
//條件一: cellsBusy == 0 true --> 表示當前無鎖狀態(tài),當前線程可以去競爭這把鎖
//條件二:casCellsBusy() true --> 表示當前線程獲取鎖成功,可以執(zhí)行擴容邏輯 ,false 表示有其他線程在做相關的操作
// 嘗試兩次之后
else if (cellsBusy == 0 && casCellsBusy()) {
try {
if (cells == as) { // Expand table unless stale
//n<<1 翻倍
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; // Retry with expanded table
}
//重置當前線程hash值
h = advanceProbe(h);
}
//情況2:前置條件,cells為進行初始化,as為null
//條件一: cellsBusy為true當前未加鎖
//條件二: cells == as 為什么? 因為其他線程在你給as賦值之后修改了cells
//條件三: true 表示獲取鎖成功 ,會把cellBusy 設置為 1 , false 表示其他線程在持有這個鎖
else if (cellsBusy == 0 && cells == as && casCellsBusy()) {
boolean init = false;
try {
// 為什么還有一次判斷呢?
//防止其他線程已經(jīng)初始化了,當前線程再次初始化,導致丟失數(shù)據(jù)
if (cells == as) {
Cell[] rs = new Cell[2];
rs[h & 1] = new Cell(x);
cells = rs;
init = true;
}
} finally {
//釋放鎖
cellsBusy = 0;
}
if (init)
break;
}
//情況三:
//1.當前cellBusy加鎖狀態(tài),表示其他線程正在初始化cells,所以當前線程累加到base
//2.cells被其他線程初始化之后,當前線程需要將數(shù)據(jù)累加到base
else if (casBase(v = base, ((fn == null) ? v + x :
fn.applyAsLong(v, x))))
break; // Fall back on using base
}
}以上就是LongAdder原理及創(chuàng)建使用示例詳解的詳細內容,更多關于LongAdder創(chuàng)建使用的資料請關注腳本之家其它相關文章!
相關文章
java讀取配置文件(properties)的時候,unicode碼轉utf-8方式
這篇文章主要介紹了java讀取配置文件(properties)的時候,unicode碼轉utf-8方式,具有很好的參考價值,希望對大家有所幫助。如有錯誤或未考慮完全的地方,望不吝賜教2022-02-02
淺談spring ioc的注入方式及注入不同的數(shù)據(jù)類型
這篇文章主要介紹了淺談spring ioc的注入方式及注入不同的數(shù)據(jù)類型,具有一定借鑒價值,需要的朋友可以參考下2017-12-12
Spring Security Oauth2.0 實現(xiàn)短信驗證碼登錄示例
本篇文章主要介紹了Spring Security Oauth2.0 實現(xiàn)短信驗證碼登錄示例,具有一定的參考價值,感興趣的小伙伴們可以參考一下2018-01-01
RabbitMQ 的消息持久化與 Spring AMQP 的實現(xiàn)詳解
這篇文章主要介紹了RabbitMQ 的消息持久化與 Spring AMQP 的實現(xiàn)剖析詳解,文中通過示例代碼介紹的非常詳細,對大家的學習或者工作具有一定的參考學習價值,需要的朋友可以參考下2019-08-08

