作者:伴川
來源:blog.csdn.net/kologin/article/details/135126371
前言
在分佈式系統中,計數器是一個常見的需求。爲了實現高併發、高可用的計數器,我們需要選擇一個合適的實現方式。
在 Java 中,有兩種常見的計數器實現方式:AtomicLong 和 LongAdder。
最近,阿里巴巴在一份技術報告中推薦使用 LongAdder ,而不是 AtomicLong
本文將介紹這兩種計數器的原理和優缺點,並分析爲什麼阿里巴巴推薦使用 LongAdder 。
推薦一個開源免費的 Spring Boot 實戰項目:
一、CAS
1.1 CAS 全稱
全稱:compare and swap,比較並交換。
雖然翻譯過來是[比較並交換],但它是一個原子性的操作,對應到CPU指令爲 cmpxchg 。
1.2 通俗理解CAS
- CAS 有三個操作數:當前值A、內存值V、要修改的新值B。
- 假設 當前值A 跟 內存值V 相等,那就將內存值V 改成B。
- 假設 當前值A 跟 內存值V 不相等,要麼就重試,要麼就放棄更新。
- 將當前值與內存值進行對比,判斷是否有被修改過,這就是CAS的核心。
1.3 CAS的問題
CAS有個缺點就是會帶來 ABA 的問題。
從CAS更新的時候,我們可以發現它只比對當前值和內存值是否相等,這會帶來個問題,下面我舉例說明下:
- 假設線程A讀到當前值是10,可能線程B把值修改爲100,然後線程C又把值修改爲10。
- 等到線程A拿到執行權時,因爲當前值和內存值是一致的,線程A是可以修改的!
- 站在線程A的角度來說,這個值是從未被修改的 。
- 這是不合理的,因爲我們從上帝的角度來看,這個變量已經被線程B和線程C修改過了。
1.4 解決 ABA 問題
要解決ABA的問題,Java 也提供了 AtomicStampedReference 類供我們用,說白了就是加了個版本,比對的就是內存值+版本是否一致。
疑問:
爲什麼阿里巴巴開發手冊提及到推薦使用 LongAdder 對象,比AtomicLong 性能更好(減少樂觀鎖的重試次數)?
原因:
因爲 AtomicLong 做累加的時候實際上就是多個線程操作同一個目標資源。
在高併發時,只有一個線程是執行成功的,其他的線程都會失敗,不斷自旋(重試),自旋會成爲瓶頸。
而 LongAdder 的思想就是把要操作的目標資源 分散,到數組 Cell 中。
每個線程對自己的 Cell 變量的 value 進行原子操作,大大降低了失敗的次數。
這就是爲什麼在高併發場景下,推薦使用 LongAdder 的原因。
二、LongAdder
2.1 什麼是 LongAdder
LongAdder是JDK1.8由Doug Lea大神新增的原子操作類,位於java.util.concurrent.atomic包下,LongAdder在高併發的場景下會比AtomicLong 具有更好的性能,代價是消耗更多的內存空間。
LongAdder是Google開源的一個高性能計數器實現。它採用了一種分段鎖的策略,將一個long型的變量分割成多個16字節的段,每個段都使用一個獨立的AtomicLong進行更新。這樣,在高併發場景下,多個線程可以同時對不同的段進行更新操作,互不干擾。
LongAdder的優點是併發性能高,適用於高併發的場景。由於採用了分段鎖的策略,LongAdder可以避免AtomicLong中的競爭問題。此外,LongAdder還支持可擴展性,可以通過增加更多的段來提高性能。但是,LongAdder的缺點是代碼相對複雜一些,需要更多的維護成本。
2.2 爲什麼推薦推薦 LongAdder
LongAdder設計思想上,採用分段的方式降低併發衝突的概率。通過維護一個基準值base和 Cell 數組:
如下圖所示:
三、AtomicLong
3.1 什麼是 AtomicLong
AtomicLong是Java提供的一個原子類,用於實現高併發的計數器。它利用了CAS(Compare-and-Swap)操作來保證線程安全。在AtomicLong中,每次計數操作都會先讀取當前值,然後使用CAS操作更新值。如果值沒有被其他線程修改過,則更新成功,否則需要重新嘗試。
AtomicLong的優點是簡單易用,性能也不錯。但是,在高併發場景下,AtomicLong可能會出現競爭問題。因爲多個線程可能同時讀取和更新同一個AtomicLong的當前值,導致數據不一致。此外,AtomicLong的CAS操作也可能因爲硬件和操作系統的原因出現失敗的情況。
3.2 爲什麼不推薦 AtomicLong
在LongAdder之前,當我們在進行計數統計的時,通常會使用AtomicLong來實現。AtomicLong能保證併發情況下計數的準確性,其內部通過CAS來解決併發安全性的問題。
如下圖所示:
圖裏可以看出在高併發情況下,當有大量線程同時去更新一個變量,任意一個時間點只有一個線程能夠成功,絕大部分的線程在嘗試更新失敗後,會通過自旋的方式再次進行嘗試,這樣嚴重佔用了CPU的時間片,進而導致系統性能問題。
四、總結
阿里巴巴推薦使用LongAdder的原因主要有以下幾點:
1.高併發性能:
LongAdder採用分段鎖的策略,可以避免AtomicLong中的競爭問題,提高併發性能。在分佈式系統中,高併發性能是非常重要的。
2.可擴展性:
LongAdder支持可擴展性,可以通過增加更多的段來提高性能。這對於需要處理大量請求的分佈式系統來說是非常有利的。
3.代碼簡單易懂:
雖然LongAdder的代碼相對複雜一些,但是相對於AtomicLong來說更容易理解和維護。這對於開發人員來說是非常重要的。
4.更好的適用場景:
阿里巴巴推薦使用LongAdder主要是因爲在分佈式系統中需要一個高性能、高可用的計數器實現。而LongAdder正好符合這個需求。
總之,阿里巴巴推薦使用LongAdder的原因主要是因爲它的高併發性能、可擴展性、代碼簡單易懂以及更好的適用場景。當然,在實際應用中還需要根據具體場景和需求進行選擇和優化。
更多文章推薦:
2.2,000+ 道 Java面試題及答案整理(2024最新版)
3.免費獲取 IDEA 激活碼的 7 種方式(2024最新版)
覺得不錯,別忘了隨手點贊+轉發哦!