Java LongAdder使用與應(yīng)用實(shí)戰(zhàn)
LongAdder是 Java 并發(fā)編程中為高并發(fā)計(jì)數(shù)而生的利器。下面這張表格能幫助你快速把握其全貌,之后我們?cè)偕钊爰?xì)節(jié)和實(shí)戰(zhàn)。
| 特性維度 | 說(shuō)明 |
|---|---|
| ?設(shè)計(jì)目標(biāo)? | 高并發(fā)場(chǎng)景下的高性能計(jì)數(shù)/累加操作,優(yōu)化多線程寫(xiě)競(jìng)爭(zhēng) 。 |
| ?核心思想? | ?分而治之,分散熱點(diǎn)?:將單一變量的競(jìng)爭(zhēng)壓力分散到多個(gè)單元(base+ Cell[]),以空間換取時(shí)間 。 |
| ?關(guān)鍵數(shù)據(jù)結(jié)構(gòu)? | base(基礎(chǔ)值) 和 Cell[](單元數(shù)組) 。 |
| ?寫(xiě)入流程? | 1. 無(wú)競(jìng)爭(zhēng)或低競(jìng)爭(zhēng)時(shí),CAS 操作直接更新 base。 2. 競(jìng)爭(zhēng)激烈時(shí),線程通過(guò)哈希映射到 Cell[]中的某個(gè)單元進(jìn)行更新,大幅減少?zèng)_突 。 |
?讀取流程 (sum())?? | 返回 base與所有 Cell單元值的累加和。此操作不保證強(qiáng)一致性,是最終一致性的,因?yàn)樵谇蠛瓦^(guò)程中可能有其他線程正在更新 。 |
| ?主要優(yōu)點(diǎn)? | 高并發(fā)寫(xiě)入性能遠(yuǎn)超 AtomicLong,有效減少 CAS 空自旋,避免高競(jìng)爭(zhēng)下的性能驟降 。 |
| ?主要缺點(diǎn)? | 更高的內(nèi)存消耗;讀取操作 (sum()) 非原子快照,是最終一致性的;不支持 compareAndSet等原子條件更新操作 。 |
| ?典型應(yīng)用場(chǎng)景? | 高頻統(tǒng)計(jì)計(jì)數(shù)器(如 API 調(diào)用次數(shù)、點(diǎn)擊量)、監(jiān)控指標(biāo)收集、頻率統(tǒng)計(jì)等“寫(xiě)多讀少”且對(duì)讀的實(shí)時(shí)精確性要求不高的場(chǎng)景 。 |
?? 深入核心原理
要理解 LongAdder的高性能,需要深入其內(nèi)部機(jī)制。
?分散熱點(diǎn)與動(dòng)態(tài)擴(kuò)容?
初始時(shí),所有線程都嘗試通過(guò) CAS 操作更新
base變量。當(dāng)并發(fā)加劇,某個(gè)線程 CAS 更新base失敗時(shí),系統(tǒng)會(huì)初始化一個(gè)Cell數(shù)組(默認(rèn)大小為 2)。每個(gè)線程會(huì)根據(jù)其唯一的哈希值(探針,Probe)被映射到數(shù)組的某個(gè)槽位(Cell),然后對(duì)該槽位內(nèi)的值進(jìn)行更新 。隨著競(jìng)爭(zhēng)持續(xù),如果線程在指定的Cell上更新仍然失敗,Cell數(shù)組會(huì)進(jìn)行擴(kuò)容(通常翻倍),直到達(dá)到與 CPU 核數(shù)相當(dāng)?shù)乃剑赃M(jìn)一步分散競(jìng)爭(zhēng) 。這種設(shè)計(jì)將針對(duì)單一內(nèi)存地址的激烈競(jìng)爭(zhēng),轉(zhuǎn)化為對(duì)多個(gè)內(nèi)存地址的相對(duì)平和的訪問(wèn)。?解決偽共享?
Cell類使用@sun.misc.Contended注解進(jìn)行填充,以避免偽共享?(False Sharing)。偽共享是指多個(gè)看似不相關(guān)的變量因位于同一個(gè) CPU 緩存行中,當(dāng)一個(gè)處理器更新其中一個(gè)變量時(shí),會(huì)導(dǎo)致整個(gè)緩存行失效,其他處理器即使使用該行內(nèi)的其他變量,也需要重新從內(nèi)存加載,造成性能損失。@Contended注解確保每個(gè)Cell對(duì)象獨(dú)立占據(jù)一個(gè)緩存行,從而提升緩存效率 。?核心方法流程?
add(long x)方法是其核心 :- 首先檢查
cells數(shù)組是否已初始化。若未初始化,則嘗試直接 CAS 更新base字段。 - 若 CAS 更新
base失敗(表明出現(xiàn)競(jìng)爭(zhēng)),則進(jìn)入沖突處理邏輯。 - 檢查
cells數(shù)組是否已初始化、當(dāng)前線程映射的Cell槽位是否存在、以及嘗試 CAS 更新該Cell的值。 - 如果以上任意一步失敗,則進(jìn)入更復(fù)雜的
longAccumulate方法。該方法會(huì)處理cells數(shù)組的初始化、擴(kuò)容,以及為線程重新計(jì)算哈希值以尋找新的空閑槽位,確保更新最終能夠完成 。
sum()方法遍歷cells數(shù)組(如果已初始化),將所有非空Cell的值與base相加返回。由于此操作沒(méi)有加鎖,在并發(fā)更新時(shí)返回的是某個(gè)時(shí)刻的近似總值,具備最終一致性而非強(qiáng)一致性 。- 首先檢查
??? 實(shí)戰(zhàn)應(yīng)用示例
LongAdder非常適用于以下場(chǎng)景:
?API 請(qǐng)求統(tǒng)計(jì)與監(jiān)控?
可以輕松統(tǒng)計(jì)服務(wù)的請(qǐng)求量、成功/失敗次數(shù)、總耗時(shí)等指標(biāo) 。
public class ApiRequestMonitor { private final LongAdder requestCount = new LongAdder(); private final LongAdder totalLatency = new LongAdder(); public void recordRequest(long latency) { requestCount.increment(); totalLatency.add(latency); } public MonitoringSnapshot getSnapshot() { // 注意:sum() 獲取的是瞬時(shí)近似值 return new MonitoringSnapshot(requestCount.sum(), totalLatency.sum()); } }?結(jié)合 ConcurrentHashMap 進(jìn)行頻率統(tǒng)計(jì)?
這是一種常見(jiàn)且高效的模式,用于統(tǒng)計(jì)元素出現(xiàn)次數(shù) 。
ConcurrentMap<String, LongAdder> freqMap = new ConcurrentHashMap<>(); public void count(String word) { // 如果鍵不存在,則原子性地放入一個(gè)新的 LongAdder freqMap.computeIfAbsent(word, k -> new LongAdder()) .increment(); // 然后遞增 } // 獲取某個(gè)詞的頻率 long frequency = freqMap.getOrDefault(word, new LongAdder()).sum();
?? 使用注意事項(xiàng)與最佳實(shí)踐
- ?理解一致性語(yǔ)義?:
LongAdder的sum()方法是最終一致性的。如果你的業(yè)務(wù)場(chǎng)景要求在任何時(shí)刻讀取都必須是完全精確的值(例如金融賬戶余額),那么AtomicLong或鎖機(jī)制更為合適 。 - ?關(guān)注內(nèi)存占用?:
Cell數(shù)組和避免偽共享的填充會(huì)帶來(lái)比AtomicLong更高的內(nèi)存開(kāi)銷。在內(nèi)存受限或并發(fā)度不高的環(huán)境中,需要權(quán)衡利弊 。 - ?避免頻繁調(diào)用
sum()?:sum()方法需要遍歷Cell數(shù)組,在數(shù)組較大時(shí)有一定開(kāi)銷。應(yīng)避免在性能關(guān)鍵路徑中頻繁調(diào)用 。 - ?重置操作?:
reset()方法將base和所有Cell置零,但此操作非原子性。通常僅在確定沒(méi)有并發(fā)更新時(shí)(如一個(gè)統(tǒng)計(jì)周期結(jié)束清零時(shí))使用 。
?? 選型指南:LongAdder vs. AtomicLong
| 場(chǎng)景 | 推薦選擇 | 理由 |
|---|---|---|
| ?極高并發(fā)寫(xiě)入,對(duì)讀的實(shí)時(shí)精確性要求不高?(如統(tǒng)計(jì)、監(jiān)控) | ?LongAdder? | 寫(xiě)吞吐量極高,通過(guò)分散競(jìng)爭(zhēng)避免性能瓶頸 。 |
| ?低并發(fā)環(huán)境,或需要頻繁讀取精確瞬時(shí)值?(如序列號(hào)生成、狀態(tài)標(biāo)志) | ?AtomicLong? | 讀取 (get()) 是強(qiáng)一致性的單次 volatile 讀,性能極高;接口豐富,支持 compareAndSet等復(fù)雜原子操作 。 |
| ?需要復(fù)雜的累加操作?(如求最大值、最小值) | ?LongAccumulator? | LongAdder是 LongAccumulator的一個(gè)特例(專用于加法)。LongAccumulator允許傳入自定義二元運(yùn)算符,功能更靈活 。 |
?? 總結(jié)
LongAdder是 Java 并發(fā)工具包中“分而治之”思想的杰出代表。它通過(guò)空間換時(shí)間,巧妙地化解了高并發(fā)下的寫(xiě)入競(jìng)爭(zhēng),在統(tǒng)計(jì)、監(jiān)控等“寫(xiě)多讀少”的場(chǎng)景下表現(xiàn)卓越。
選擇 LongAdder的關(guān)鍵在于明確:?你是否愿意用讀取操作的強(qiáng)一致性和更高的內(nèi)存開(kāi)銷,來(lái)?yè)Q取極高的并發(fā)寫(xiě)入性能。
到此這篇關(guān)于Java LongAdder使用與應(yīng)用實(shí)戰(zhàn)的文章就介紹到這了,更多相關(guān)Java LongAdder使用內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
Mybatis select記錄封裝的實(shí)現(xiàn)
這篇文章主要介紹了Mybatis select記錄封裝的實(shí)現(xiàn),文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)學(xué)習(xí)吧2020-10-10
詳解使用MyBatis Generator自動(dòng)創(chuàng)建代碼
這篇文章主要介紹了使用MyBatis Generator自動(dòng)創(chuàng)建代碼,小編覺(jué)得挺不錯(cuò)的,現(xiàn)在分享給大家,也給大家做個(gè)參考。一起跟隨小編過(guò)來(lái)看看吧2017-12-12
MybatisPlusException:Failed?to?process,Error?SQL異常報(bào)錯(cuò)的解決辦法
這篇文章主要給大家介紹了關(guān)于MybatisPlusException:Failed?to?process,Error?SQL異常報(bào)錯(cuò)的解決辦法,文中通過(guò)實(shí)例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友可以參考下2023-03-03
JAVA文件讀寫(xiě)例題實(shí)現(xiàn)過(guò)程解析
這篇文章主要介紹了JAVA文件讀寫(xiě)例題實(shí)現(xiàn)過(guò)程解析,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友可以參考下2020-06-06
Lombok的詳細(xì)使用及優(yōu)缺點(diǎn)總結(jié)
最近在學(xué)Mybatis,接觸到了Lombok的使用,所以寫(xiě)一篇文章記錄一下,包括lombok的安裝及使用優(yōu)缺點(diǎn),感興趣的朋友跟隨小編一起看看吧2021-07-07
idea2020中復(fù)制一個(gè)微服務(wù)實(shí)例的方法
這篇文章主要介紹了idea2020中復(fù)制一個(gè)微服務(wù)實(shí)例的方法,本文給大家介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或工作具有一定的參考借鑒價(jià)值,需要的朋友可以參考下2020-09-09

