LongAdder原理及創(chuàng)建使用示例詳解
LongAdder介紹
1.Atomic原子類
Atomic的原子類內(nèi)部使用的是CAS原則,CAS是一個樂觀鎖,但是如果是在高并發(fā)的情況下的話,多個線程不斷地競爭
CAS的不斷的自旋,非常耗CPU.
高并發(fā)環(huán)境下,value變量其實是一個熱點,也就是多個線程競爭一個熱點。
這時候就需要使用的 LongAdder 來替代 Atomic類
2.LongAdder原理
LongAdder的原理就是分散熱點,將value分散到一個數(shù)組中,不同的線程去找自己的對應(yīng)的Cell進(jìn)行修改值,
各個線程對Cell進(jìn)行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ù)組了
知道對應(yīng)的桶位添加值或者修改值
我感覺你已經(jīng)不知道我在說什么了!
反正你就知道,如果有多個線程發(fā)生了競爭,就去cells數(shù)組中找對應(yīng)的桶位的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ù)組還沒有進(jìn)行初始化,
創(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
通過路由尋址公式找到對應(yīng)的索引位置的桶位為空,然后把創(chuàng)建的Cell對象存儲進(jìn)去
更直白就是說,你要蹲坑了,你要去找坑,如果坑位沒有人,你就進(jìn)去
3.3.擴(kuò)容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倍擴(kuò)容
4.總結(jié)
你現(xiàn)在肯定是云里霧里,講的都是什么東西.
你現(xiàn)在就只需要知道 LongAdder
通過 base 和 Cell數(shù)組 換取更高的性能
5.附上源碼解析
當(dāng)然這里你可以跳過了,不用看了
5.1.add方法
public void add(long x) { // as 表示cells引用 // b 表示獲取的base值 // v 表示期望值 // m 表示cells數(shù)組的長度 // a 表示當(dāng)前線程命中cell單元格 Cell[] as; long b, v; int m; Cell a; //條件一: --> true 表示cells已經(jīng)初始化過了,當(dāng)前線程應(yīng)該把數(shù)據(jù)寫到對應(yīng)的cell中 // --> false 表示cells還沒有初始化過,當(dāng)前線程所有的數(shù)據(jù)都被寫到base中 //條件二: --> false 表示cas替換值成功 // --> true 表示發(fā)生競爭了,可能需要重試 或者 擴(kuò)容 if ((as = cells) != null || !casBase(b = base, b + x)) { //什么時候會進(jìn)來? //1.true 表示cells已經(jīng)初始化過了,當(dāng)前線程應(yīng)該把數(shù)據(jù)寫到對應(yīng)的cell中 //2.true cells還沒有初始化,但是發(fā)生競爭了 // true -> 未競爭 false ->發(fā)生競爭 boolean uncontended = true; //as == null || (m = as.length - 1) < 0 看做是一個條件 //條件一:true --> 說明cells還沒有被初始化,也就是多線程寫base發(fā)生競爭了進(jìn)來的,也就是通過第二個條件進(jìn)來的 // false --> 說明cells已經(jīng)初始化了,可以去尋找自己的cell進(jìn)行賦值 //條件二: getProbe() 可以理解為獲取當(dāng)前線程的hash值, m表示cells長度-1 注意:cells的次方數(shù)一定是2的次方 // true --> 說明當(dāng)前線程對應(yīng)的下標(biāo)的cell為空,需要創(chuàng)建 longAccumulate 支持 // false --> 說明當(dāng)前線程對應(yīng)的下標(biāo)cell不為空,下一步需要將 x,添加到cell中 //條件三: true --> 代表cas失敗,代表當(dāng)前線程對應(yīng)的cell,有競爭 // false --> 表示cas成功 if (as == null || (m = as.length - 1) < 0 || (a = as[getProbe() & m]) == null || !(uncontended = a.cas(v = a.value, v + x))) //都哪些情況會調(diào)用這里的方法? //1.true --> 說明cells還沒有被初始化,也就是多線程寫base發(fā)生競爭了進(jìn)來的 //2.true --> 說明當(dāng)前線程對應(yīng)的下標(biāo)的cell為空,需要創(chuàng)建 longAccumulate 支持 //3.true --> 代表cas失敗,代表當(dāng)前線程對應(yīng)的cell,有競爭 longAccumulate(x, null, uncontended); } }
5.2.longAccumulate方法
/** * //1.true --> 說明cells還沒有被初始化,也就是多線程寫base發(fā)生競爭了進(jìn)來的,到時候會進(jìn)行初始化cell * //2.true --> 說明當(dāng)前線程對應(yīng)的下標(biāo)的cell為空,需要創(chuàng)建 longAccumulate 支持 * //3.true --> 對應(yīng)的下標(biāo)也有cell,但是cas失敗,代表當(dāng)前線程對應(yīng)的cell,有競爭 */ //wasUncontended,只有cells初始化之后,并且當(dāng)前線程 競爭修改失敗,才會是false,其他情況下都是true final void longAccumulate(long x, LongBinaryOperator fn, boolean wasUncontended) { //表示線程的hash值 int h; //條件成立,說明當(dāng)前線程還未分配hash值 if ((h = getProbe()) == 0) { //給當(dāng)前線程分配hash值 ThreadLocalRandom.current(); // force initialization //取出當(dāng)前線程的hash值賦值給h h = getProbe(); //為什么?默認(rèn)情況下,可定寫入到了cell[0]的位置,不把他當(dāng)做一次真正的競爭?? wasUncontended = true; } //表示擴(kuò)容意向, false表示不會擴(kuò)容 , true有可能會擴(kuò)容 boolean collide = false; // True if last slot nonempty //自旋 for (;;) { //as 表示cells引用 //a 表示當(dāng)前線程命中的cell //n 表示cell的數(shù)組長度 //v 表示期望值 Cell[] as; Cell a; int n; long v; //情況1:as不為空表示cells已經(jīng)初始化了,當(dāng)前線程應(yīng)該將數(shù)據(jù)寫入到對應(yīng)的cell中 if ((as = cells) != null && (n = as.length) > 0) { //2.true --> 說明當(dāng)前線程對應(yīng)的下標(biāo)的cell為空,需要創(chuàng)建 longAccumulate 支持 //3.true --> 代表cas失敗,代表當(dāng)前線程對應(yīng)的cell,有競爭[重試或者擴(kuò)容] //情況1.1: true 表示當(dāng)前線程對應(yīng)的下標(biāo)位置的cell為null,需要創(chuàng)建cell if ((a = as[(n - 1) & h]) == null) { //true 表示當(dāng)前鎖未被占用,false-->表示鎖被占用 if (cellsBusy == 0) { // Try to attach new Cell //拿x創(chuàng)建cell Cell r = new Cell(x); // Optimistically create //條件一:true 表示當(dāng)前鎖未被占用,false-->表示鎖被占用 //條件二:true表示當(dāng)前線程獲取鎖成功,false表示當(dāng)前線程獲取鎖失敗, if (cellsBusy == 0 && casCellsBusy()) { //是否創(chuàng)建成功的標(biāo)記 boolean created = false; try { // Recheck under lock //rs表示cells引用 //m 表示cells長度 //j 表示當(dāng)前線程命中的下標(biāo) Cell[] rs; int m, j; //條件一,條件二,恒成立的 //rs[j = (m - 1) & h] == null 為了防止其他線程已經(jīng)初始化話過了,防止當(dā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 } } //擴(kuò)容意向改為false collide = false; } //情況1.2 //wasUncontended,只有cells初始化之后,并且當(dāng)前線程 競爭修改失敗,才會是false,其他情況下都是true else if (!wasUncontended) // CAS already known to fail wasUncontended = true; // Continue after rehash //情況1.3 :當(dāng)前線程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 --> 擴(kuò)容意向改為False ,表示不擴(kuò)容了 false --> 表示cells還可以擴(kuò)容 //條件二: true --> 表示其他線程已經(jīng)擴(kuò)容過了,當(dāng)前線程rehash之后重試即可 else if (n >= NCPU || cells != as) collide = false; // At max size or stale //默認(rèn)開始為false 的, 設(shè)置為true,需要擴(kuò)容,但是不一定真的發(fā)生擴(kuò)容 else if (!collide) collide = true; //情況6.真正的擴(kuò)容邏輯 //條件一: cellsBusy == 0 true --> 表示當(dāng)前無鎖狀態(tài),當(dāng)前線程可以去競爭這把鎖 //條件二:casCellsBusy() true --> 表示當(dāng)前線程獲取鎖成功,可以執(zhí)行擴(kuò)容邏輯 ,false 表示有其他線程在做相關(guān)的操作 // 嘗試兩次之后 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 } //重置當(dāng)前線程hash值 h = advanceProbe(h); } //情況2:前置條件,cells為進(jìn)行初始化,as為null //條件一: cellsBusy為true當(dāng)前未加鎖 //條件二: cells == as 為什么? 因為其他線程在你給as賦值之后修改了cells //條件三: true 表示獲取鎖成功 ,會把cellBusy 設(shè)置為 1 , false 表示其他線程在持有這個鎖 else if (cellsBusy == 0 && cells == as && casCellsBusy()) { boolean init = false; try { // 為什么還有一次判斷呢? //防止其他線程已經(jīng)初始化了,當(dāng)前線程再次初始化,導(dǎo)致丟失數(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.當(dāng)前cellBusy加鎖狀態(tài),表示其他線程正在初始化cells,所以當(dāng)前線程累加到base //2.cells被其他線程初始化之后,當(dāng)前線程需要將數(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)建使用示例詳解的詳細(xì)內(nèi)容,更多關(guān)于LongAdder創(chuàng)建使用的資料請關(guān)注腳本之家其它相關(guān)文章!
相關(guān)文章
RocketMq同組消費(fèi)者如何自動設(shè)置InstanceName
這篇文章主要介紹了RocketMq同組消費(fèi)者如何自動設(shè)置InstanceName問題,具有很好的參考價值,希望對大家有所幫助,如有錯誤或未考慮完全的地方,望不吝賜教2024-06-06詳解OpenCV For Java環(huán)境搭建與功能演示
這篇文章主要介紹了x詳解OpenCV For Java環(huán)境搭建與功能演示,小編覺得挺不錯的,現(xiàn)在分享給大家,也給大家做個參考。一起跟隨小編過來看看吧2018-04-04Java多線程基礎(chǔ) 線程的等待與喚醒(wait、notify、notifyAll)
這篇文章主要介紹了Java多線程基礎(chǔ) 線程的等待與喚醒,需要的朋友可以參考下2017-05-05詳解static 和 final 和 static final區(qū)別
這篇文章主要介紹了static 和 final 和 static final區(qū)別,文中通過示例代碼介紹的非常詳細(xì),對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧2019-04-04spring boot動態(tài)切換數(shù)據(jù)源的實現(xiàn)
這篇文章主要介紹了spring boot動態(tài)切換數(shù)據(jù)源的實現(xiàn),文中通過示例代碼介紹的非常詳細(xì),對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧2021-01-01JAVA正則表達(dá)式及字符串的替換與分解相關(guān)知識總結(jié)
今天給大家?guī)淼氖顷P(guān)于Java的相關(guān)知識總結(jié),文章圍繞著JAVA正則表達(dá)式及字符串的替換與分解展開,文中有非常詳細(xì)的介紹及代碼示例,需要的朋友可以參考下2021-06-06