Java原子化讀并且寫操作中存在的問題

文章前記


程序員工作久了便可能整日忙碌于“增刪改查”中,迷失方向,毫無進步。

該公眾號致力于分享軟件開發(fā)相關(guān)的原創(chuàng)干貨,助你完成從程序員到架構(gòu)師的進階之路!

努力!做一個NB的Coder!



1??背景


之前的文章中我們已經(jīng)講過,Java的AtomicInteger類中能夠?qū)⒆x和寫封裝成為一個原子操作,例如其中的getAndIncrement()方法就可以實現(xiàn)原子化的i++操作。

這一切的實現(xiàn)是通過系統(tǒng)原生的CAS操作實現(xiàn)的。

CAS操作即比較并交換操作,能夠在內(nèi)存真值與預(yù)期原值一樣時,將新值放入指定的內(nèi)存中。

本文我們探討基于CAS操作實現(xiàn)的讀寫原子化中引發(fā)的問題。



2??CAS存在的問題


CAS實現(xiàn)了高效的原子操作,但是仍然存在一些問題,主要有三個:

1 ABA問題

2 循環(huán)開銷問題

3 無法應(yīng)用與多個共享變量


2.1? ABA問題

因為CAS需要在操作值的時候檢查下值有沒有發(fā)生變化,如果沒有發(fā)生變化則更新,但是如果一個值原來是A,變成了B,又變成了A,那么使用CAS進行檢查時會發(fā)現(xiàn)它的值沒有發(fā)生變化,認為情況還是樂觀的。此時可能引發(fā)錯誤。

例如,時刻t1在隊列中CAS操作前獲取預(yù)期原值A(chǔ)=5,之后隊列發(fā)生了移動,再次獲取的原值仍然為5,此時進行了CAS操作。則會引發(fā)錯誤。

ABA問題的解決思路就是使用版本號。在變量前面追加上版本號,每次變量更新的時候把版本號加一,那么A-B-A 就會變成1A-2B-3A。

Java1.5開始,atomic包中存在一個AtomicStampedReference類來解決ABA問題。

該類的compareAndSet方法會先檢查當(dāng)前引用是否發(fā)生變化,如果沒有變化才會使用CAS更新其值。

源代碼如下:

2.2? 循環(huán)開銷問題

因為CAS是基于樂觀鎖的思想的,需要不斷判斷樂觀情況是否成立,因此是一個循環(huán)操作,常被稱為CAS自旋。如果并發(fā)嚴重,則CAS自旋會不斷嘗試,導(dǎo)致CPU開銷大。

解決此問題的辦法是在多次CAS操作失敗時,能夠暫停一段時間,在進行CAS操作。防止在其他線程密集修改某變量時對該變量不斷進行CAS自旋。當(dāng)然,這需要JVM的支持。


2.3? 無法應(yīng)用于多個共享變量

對一個共享變量進行CAS操作時可以的,那如果對一組變量展開操作呢?顯然是不可以的,因為內(nèi)存位置V是一個值,而非一組值。這個時候只能使用鎖。

其實,還有一個辦法,即將這一組變量封裝成一個對象,從而將對一組變量的操作轉(zhuǎn)化為對一個對象的操作。

Java1.5之后,便可以使用AtomicReference來封裝一個需要原子化更新的對象。

3?總結(jié)

雖然,AtomicInteger類中的原子化操作存在一些問題,但是它與加同步鎖的方法相比仍然在性能、易用性上具有巨大的優(yōu)勢。

希望大家能夠在日常的編碼中掌握并使用AtomicInteger類中的相關(guān)方法,寫出簡潔、高效的代碼。



—END—


微信公眾號:程序員進階架構(gòu)師

分享讓你從程序員進階架構(gòu)師的原創(chuàng)干貨!

歡迎關(guān)注我們,不錯過每期的原創(chuàng)干貨!

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容