Java的原子類無鎖并發(fā)利器詳解
前言引入
當(dāng)存在如下場景,兩個(gè)線程同時(shí)去將count值累加一萬次,那么如下代碼是否存在線程安全問題呢?
public class Test2 {
public static void main(String[] args) throws InterruptedException {
TestCount testCount = new TestCount();
Thread T1 = new Thread(()->{
testCount.add10k();
});
Thread T2 = new Thread(()->{
testCount.add10k();
});
T1.start();
T2.start();
T1.join();
T2.join();
System.out.println(testCount.count);
}
}
class TestCount{
long count = 0;
void add10k(){
int idx = 0;
while (idx++ < 10000){
count += 1;
}
}
}顯然是存在的,最終的結(jié)果顯然是小于兩萬的,原因是count值存在可見性問題,count+=1也存在原子性的問題。
一般思路都是將count采用volatile修飾,count+=1原子性問題采用synchronized互斥鎖解決。
class TestCount{
volatile long count = 0;
synchronized void add10k(){
int idx = 0;
while (idx++ < 10000){
count += 1;
}
}
}但是對于這種簡單的互斥操作,需要采用synchronized這種比較重的互斥鎖嗎?有沒有更優(yōu)的解決辦法呢?當(dāng)然存在,采用原子類無鎖方案能夠極大的提升性能
class TestCount{
AtomicLong count = new AtomicLong(0);
void add10k(){
int idx = 0;
while (idx++ < 10000){
count.getAndIncrement();
}
}
}原子類同樣能夠解決互斥性問題、原子性問題除此之外,因?yàn)樵宇愂菬o鎖操作,沒有用互斥鎖解決帶來的加鎖解決性能消耗,這種絕佳方案是怎么做到的呢?
無鎖方案實(shí)現(xiàn)原理
無鎖方案之所以能夠保證原子性,主要還是硬件保證,CPU為了解決并發(fā)問題,提供了CAS(Compare And Swap)指令即比較并交換,CAS一般包含三個(gè)參數(shù),共享變量的內(nèi)存地址A,用于比較的期望值B,更新共享變量C,當(dāng)共享變量的內(nèi)存地址A的值和共享變量B的值相等時(shí),才將共享變量的內(nèi)存地址A處的值更新為共享變量C。
將場景語義化如下
class SimpleCAS{
int count;
public synchronized int cas(int expect,int newCount){
// 讀取count值
int oldcount = count;
// 讀取的count值和期望值比較
if (oldcount == expect){
count = newCount;
}
// 返回老值
return oldcount;
}
}CAS指令判斷并不是一次性的,如果比較失敗又會(huì)重新取最新的值和期望值判斷直到成功。
class SimpleCAS{
volatile int count;
public synchronized int cas(int expect,int newCount){
// 讀取count值
int oldcount = count;
// 讀取的count值和期望值比較
if (oldcount == expect){
count = newCount;
}
// 返回老值
return oldcount;
}
// 自旋操作,執(zhí)行cas方法
public void addOne(){
int newCount = 0;
do {
newCount = count + 1;
}while (count != cas(count,newCount));
}
}ABA問題
原子類雖然好用,但是一定需要的坑就是ABA問題,假如存在共享變量A值為5,線程T1將共享變量A的值改為2,而線程T2將共享變量A改為3,線程T3又將共享變量A改為2,那么對于線程T1來講共享變量A是沒有變的嗎?顯然不是,可能大多數(shù)場景我們并不關(guān)心ABA問題,對于基礎(chǔ)數(shù)據(jù)遞增可能認(rèn)為值不變就夠了,并不關(guān)心值是否已經(jīng)修改,但是對于引用類型呢,這就一定要注意ABA問題,兩個(gè)A雖然相等,但是屬性可能已經(jīng)發(fā)生變化。
原子類提供工具類解決ABA問題AtomicStampedReference和AtomicMarkableReference
public static void main(String[] args) throws InterruptedException {
// 初始化原子類 定義初始引用和標(biāo)識(shí)
// AtomicMarkableReference同理可得,只是將版本戳換成了boolean類型
AtomicStampedReference<String> reference = new AtomicStampedReference<>("zhangsan",1001);
/**
* expectedReference 期望的引用
* newReference 新的引用
* expectedStamp 期望的版本戳
* newStamp 新的版本戳
* 只有當(dāng)期望引用和期望版本戳都符合實(shí)際版本戳和引用才能替換成功
*/
reference.compareAndSet("zhangsan","lisi",1002,1003);
// zhangsan 替換失敗的原因是期望版本戳和實(shí)際版本戳不匹配
System.out.println(reference.getReference());
reference.compareAndSet("zhangsan","lisi",1001,1002);
// lisi 替換成功
System.out.println(reference.getReference());
reference.compareAndSet("lisi1","wangwu",1002,1003);
// lisi 替換失敗的原因是期望引用和實(shí)際引用不匹配
System.out.println(reference.getReference());
}getAndIncrement源碼分析
/**
* this 指當(dāng)前對象
* valueOffset 指內(nèi)存地址偏移量
*/
public final int getAndIncrement() {
return unsafe.getAndAddInt(this, valueOffset, 1);
}
/**
* this 指當(dāng)前對象
* valueOffset 指內(nèi)存地址偏移量
*/
public final int getAndAddInt(Object var1, long var2, int var4) {
int var5;
do {
// 就是讀取主內(nèi)存的值
var5 = this.getIntVolatile(var1, var2);
// this.compareAndSwapInt方法就是對應(yīng)上訴的cas方法,不過返回值是boolean類型
// var5讀取的主內(nèi)存值
// 更新成功返回true,跳出循環(huán)
} while(!this.compareAndSwapInt(var1, var2, var5, var5 + var4));
return var5;
}原子工具類總覽
原子工具類是一個(gè)大家族,根據(jù)使用可以分為原子化基本數(shù)據(jù)類型、原子化的對象引用類型、原子化數(shù)組、原子化對象屬性更新器和原子化的累加器,方法都基本類似不需要刻意去記,需要用到的時(shí)候再來查就可以,但是需要有個(gè)印象,如下圖所示。

到此這篇關(guān)于Java的原子類無鎖并發(fā)利器詳解的文章就介紹到這了,更多相關(guān)原子類無鎖并發(fā)利器內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
springboot簡單實(shí)現(xiàn)單點(diǎn)登錄的示例代碼
本文主要介紹了springboot簡單實(shí)現(xiàn)單點(diǎn)登錄的示例代碼,文中通過示例代碼介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2022-01-01
Spring Boot中定時(shí)任務(wù)Cron表達(dá)式的終極指南最佳實(shí)踐記錄
本文詳細(xì)介紹了SpringBoot中定時(shí)任務(wù)的實(shí)現(xiàn)方法,特別是Cron表達(dá)式的使用技巧和高級用法,從基礎(chǔ)語法到復(fù)雜場景,從快速啟用到調(diào)試驗(yàn)證,再到常見問題的解決,涵蓋了定時(shí)任務(wù)開發(fā)的全過程,感興趣的朋友一起看看吧2025-03-03
Java實(shí)現(xiàn)設(shè)計(jì)模式之責(zé)任鏈模式
責(zé)任鏈模式是一種行為設(shè)計(jì)模式,允許你將請求沿著處理鏈發(fā)送,然后處理者都可對其進(jìn)行處理,完成后可以再將其傳遞給下一個(gè)處理者。下面將會(huì)舉例說明什么是責(zé)任鏈模式,責(zé)任鏈模式該如何使用2022-08-08
Java使用Spire.PDF for Java實(shí)現(xiàn)旋轉(zhuǎn)PDF頁面
在日常的文檔處理工作中,我們經(jīng)常會(huì)遇到 PDF 頁面方向不正確的問題,本文將深入探討如何利用 Java 實(shí)現(xiàn) PDF 頁面的旋轉(zhuǎn)操作,感興趣的小伙伴可以了解下2025-09-09
java中http請求之restTemplate配置超時(shí)時(shí)間問題解決
這篇文章主要介紹了java中http請求之restTemplate配置超時(shí)時(shí)間,本文給大家分享三種解決方法,結(jié)合實(shí)例代碼給大家介紹的非常詳細(xì),對大家的學(xué)習(xí)或工作具有一定的參考借鑒價(jià)值,需要的朋友可以參考下2023-05-05
java8 Stream list to Map key 重復(fù) value合并到Collectio的操作
這篇文章主要介紹了java8 Stream list to Map key 重復(fù) value合并到Collectio的操作,具有很好的參考價(jià)值,希望對大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2021-06-06

