關於Java中的Atomic

爲什麼會出現Atomic類

在多線程或者併發環境中,我們常常會遇到這種情況 int i=0; i++ 但這種寫法是線程不安全的。
爲了達到線程安全的目的,我們通常會用synchronized來修飾對應的代碼塊。還有一種辦法就是使用J.U.C包下的atomic類
Atomic類的原理是什麼呢
atomic類是通過自旋CAS操作volatile變量實現的

CAS(O,V,A)

V:主內存存放的實際變量值
O:當前線程認爲主內存的變量值
A:希望將變量替換的值
主內存:num= 1;—>經過線程一變成0
線程1: num= 0; cas(1,1,0) O=V,認爲此時沒有線程修改主內存值,此時將0,更新到主內存
線程2: num= 0; cas(1,0,0) O!=V,認爲已經有線程修改了這個值,此時修改失敗,返回主內存的最新值O,再次重試

使用volatile修飾變量是爲了多個線程間變量的值能及時同步。也就是所謂的可見性,是指當一條線程修改了共享變量的值,新值對於其他線程來說是可以立即得知的

爲什麼使用Atomic類

因爲volatile雖然比synchronized性能要好一些,但是由於例如i++這種操作不符合原子性,而是個複合操作。我們可以簡單講這個操作理解爲由這三步組成:

1.讀取
2.加一
3.賦值

所以,在多線程環境下,有可能有線程將num讀取到本地內存中,此時其他線程可能已經將num增大了很多,當前線程依然對過期的num進行自加,重新寫到主存中,最終導致了num的結果不合預期
所以此時可以使用java併發包中的原子操作類原子操作類是通過循環CAS的方式來保證其原子性的。

ABA問題:
 對於一箇舊的變量值A,線程2將A的值改成B又改成可A,此時線程1通過CAS看到A並沒有變化,但實際A已經發生了變化,這就是ABA問題。解決這個問題的方法很簡單,記錄一下變量的版本就可以了,在變量的值發生變化時對應的版本也做出相應的變化,然後CAS操作時比較一下版本就知道變量有沒有發生變化。
atomic包下AtomicStampedReference類實現了這種思路。Mysql中Innodb的多版本併發鎖也是這個原理。
CAS機制會產生ABA問題:
num = 0;—> 1—>0(線程1線程2串行,線程1線程3並行)
線程1:cas(0,0,1) 1替換成主內存
線程2:cas(1,1,0) 1替換成了0
線程3:cas(0,0,5) 0替換成了5
產生了線程二的數據髒讀,避免思路:可以添加一個版本號,即修改的次數,判斷當前值到底有沒有被改

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章