一不小心就翻車(chē)!volatile 和 Atomic 的區(qū)別,你真的清楚嗎?
引言
小米最近在準(zhǔn)備社招面試,畢竟在互聯(lián)網(wǎng)這條“卷”路上,稍微停滯不前,就可能被更年輕的程序員們彎道超車(chē)。為了在面試中脫穎而出,小米最近瘋狂刷面試題,尤其是多線程相關(guān)的知識(shí)。
這天,小米和面試官約了一場(chǎng)線上面試,一上來(lái)面試官就丟出了一個(gè)高頻問(wèn)題:
“Java 中 volatile 變量和 Atomic 變量有什么不同?”
小米心里一緊,心想:“這不就是面試八股文里常見(jiàn)的問(wèn)題嗎?” 于是,他開(kāi)始了自信的作答……
volatile 變量:能見(jiàn)度好,但不保證原子性
1. volatile 是什么?
小米微微一笑,說(shuō)道:
volatile 關(guān)鍵字的作用是保證變量的可見(jiàn)性,防止指令重排序。
通俗點(diǎn)說(shuō),Java 的 volatile 變量類(lèi)似于“全員廣播”模式。每當(dāng)某個(gè)線程修改了這個(gè)變量,其他線程都能立刻看到最新的值,不會(huì)出現(xiàn)“臟數(shù)據(jù)”。
2. volatile 如何保證可見(jiàn)性?
JMM(Java 內(nèi)存模型) 規(guī)定,volatile 變量在寫(xiě)入時(shí),會(huì)多做一步操作:
- 普通變量賦值: 線程先從主內(nèi)存中讀取數(shù)據(jù),復(fù)制到自己的工作內(nèi)存,修改后再寫(xiě)回主內(nèi)存,其他線程無(wú)法感知這個(gè)變化。
- volatile 變量賦值: 直接刷新到主內(nèi)存,并讓所有線程的緩存失效,從而保證所有線程都能立即看到最新的值。
就像是小米早上給群發(fā)了一條消息:“今天團(tuán)建改到 3 點(diǎn)!” 這樣,所有人都能立即收到,而不會(huì)有人還在按照原來(lái)的時(shí)間去行動(dòng)。
3. 但 volatile 不能保證原子性
面試官點(diǎn)了點(diǎn)頭,然后笑著問(wèn):“那 volatile 變量能保證原子性嗎?”
小米立刻搖頭:“不能!”
他舉了個(gè)例子:
圖片
看上去 count++ 只是一個(gè)簡(jiǎn)單的自增操作,但實(shí)際上它是三步操作:
- 讀取 count 的值
- 執(zhí)行 +1 操作
- 將新值寫(xiě)回 count
如果有兩個(gè)線程 A 和 B 同時(shí)執(zhí)行 increment() 方法:
- A 讀取 count=0,執(zhí)行 count+1,還沒(méi)來(lái)得及寫(xiě)回
- B 也讀取 count=0,執(zhí)行 count+1,然后寫(xiě)回
- A 最后寫(xiě)回 count=1
本來(lái)執(zhí)行兩次 increment(),結(jié)果 count 還是 1,丟失了一次更新!這就是“并發(fā)問(wèn)題”。
所以,volatile 僅僅保證了變量的可見(jiàn)性,并不能保證操作的原子性!
Atomic 變量:天生線程安全,保證原子性
小米接著說(shuō)道:“如果想保證原子性,Java 提供了 java.util.concurrent.atomic 包,里面的 Atomic 變量是更好的選擇?!?/p>
1. 什么是 Atomic 變量?
Atomic 變量 依靠 CAS(Compare-And-Swap) 機(jī)制來(lái)保證原子性。
還是剛才的例子,如果我們改用 AtomicInteger:
圖片
這樣,多個(gè)線程同時(shí)調(diào)用 increment() 方法時(shí),就不會(huì)丟失數(shù)據(jù)了。
2. Atomic 如何保證原子性?
Atomic 變量的核心是 CAS(比較并交換,Compare-And-Swap),它的原理是:
- 讀取舊值 oldValue
- 計(jì)算新值 newValue
- 使用 CPU 指令 嘗試將 oldValue 更新為 newValue
- 如果 oldValue 還是舊值,就更新成功;如果已經(jīng)被其他線程修改了,就重新嘗試。
這種方式避免了傳統(tǒng)的 鎖(synchronized) 帶來(lái)的性能問(wèn)題,CAS 是無(wú)鎖并發(fā)的基礎(chǔ),比 synchronized 更高效!
不過(guò),CAS 也不是萬(wàn)能的,它可能導(dǎo)致“ABA 問(wèn)題”(變量值在中間被修改了,但最終又變回去了),解決方案是 AtomicStampedReference 這種帶版本號(hào)的原子變量。
volatile vs Atomic:到底該選誰(shuí)?
面試官看小米講得這么清楚,問(wèn)道:“所以,在實(shí)際開(kāi)發(fā)中,volatile 和 Atomic 該怎么選呢?”
小米總結(jié)道:
圖片
如果只是需要保證變量的可見(jiàn)性,而不涉及并發(fā)修改,可以使用 volatile,比如:
- 線程間的標(biāo)志位
- double check 機(jī)制的 instance 變量
如果需要保證變量的原子性,應(yīng)該使用 Atomic 變量,比如:
- 計(jì)數(shù)器(AtomicInteger)
- 線程安全的累加器(LongAdder)
面試官的加分題
面試官滿意地點(diǎn)了點(diǎn)頭,然后問(wèn):“那 synchronized 和 Atomic 有什么不同呢?”
小米心想:“這不就是送分題嗎?”
他笑著回答:
- synchronized 是一種阻塞式的同步機(jī)制,線程會(huì)進(jìn)入阻塞狀態(tài),性能開(kāi)銷(xiāo)較大。
- Atomic 變量是非阻塞的,它基于 CAS 操作,不會(huì)讓線程進(jìn)入阻塞狀態(tài),因此更高效。
- synchronized 適合需要多個(gè)變量一起同步的場(chǎng)景,而 Atomic 適合單個(gè)變量的無(wú)鎖操作。
面試官點(diǎn)頭:“不錯(cuò),今天的面試到這里,回去等通知吧!”
小米掛斷電話,感覺(jué)這次面試發(fā)揮得不錯(cuò),心里暗自高興。為了幫助更多正在找工作的朋友,他在朋友圈里分享了今天的面試知識(shí)點(diǎn):
- volatile 只保證可見(jiàn)性,不保證原子性
- Atomic 變量使用 CAS 機(jī)制,保證原子性,適用于計(jì)數(shù)、累加等操作
- synchronized 適用于多個(gè)變量的同步,Atomic 適用于單個(gè)變量的高效無(wú)鎖操作