何為可見性瞧毙?
線程A修改了共享變量Var1,線程B能看到這個修改嗎您访?這就是所謂的可見性。
在多線程的世界里,每個線程都有自己的工作內存峻凫,里面存儲著各種變量命锄。線程方法中的局部變量不會有同步的問題侦讨。但是贴谎,不同線程之間的共享變量就不一定了鲫构。最典型的共享變量就是某個類的靜態(tài)成員。對于這些共享變量玫坛,每個線程都會把共享變量拷貝一份副本结笨,因為JVM只允許線程修改自己工作內存中的變量值。換言之湿镀,不同線程變量之間的傳遞必須經過主內存炕吸。如下圖:
這樣一來,線程1如果修改了共享變量勉痴,線程2不一定能夠看到赫模。除非線程1在更新自己的副本之后,還能把線程副本中的新值寫入到主內存中蒸矛。同時瀑罗,線程2能重新從主內存中刷新這個共享變量。
另外雏掠,還有一個現(xiàn)象就是指令重排序斩祭。它是編譯器或處理器為了提高程序性能而做的優(yōu)化。有如下幾種:
- 編譯器優(yōu)化的重排序(編譯器優(yōu)化)
- 指令級并行重排序(處理器優(yōu)化)
- 內存系統(tǒng)的重排序(處理器優(yōu)化)
這個重排序會導致代碼書寫的順序與實際執(zhí)行順序不同的現(xiàn)象乡话。
重排序不會給單線程帶來內存可見性的問題摧玫。但是,多線程中程序交錯執(zhí)行時蚊伞,重排序可能會造成內存可見性問題席赂。下圖是《Java并發(fā)編程實踐》中的一個例子吮铭,能說明可見性的問題时迫。
綜上,多線程情況下谓晌,某個線程中變量值的修改可能在其他線程中看不到掠拳,這樣的問題就是多線程下的可見性問題。
以下方法可以解決可見性問題纸肉。
1溺欧、synchronized加鎖
JMM關于synchronized的兩條規(guī)定:
- 線程解鎖前喊熟,必須把共享變量的最新值刷新到主內存中。
- 線程加鎖時姐刁,將清空工作內存中共享變量的值芥牌,從而使用共享變量時需要從內存中重新讀取最新的值(注意:加鎖與解鎖需要是同一把鎖)。
如此一來聂使,線程解鎖前對共享變量的修改壁拉,其他線程可見。
2柏靶、volatile
- 禁止重排序優(yōu)化來保證內存可見性弃理。
- 及時刷新主內存的值。當對volatile變量執(zhí)行寫操作時屎蜓,會在寫操作后加入一條store屏障指令痘昌;當對volatile變量執(zhí)行讀操作時,會在讀操作前加入一條load屏障指令炬转。這樣一寫一讀辆苔,保證了內存可見性。
volatile關鍵字使用注意事項
- 不能保證volatile變量復合操作的原子性返吻。
- 在多線程中安全的使用volatile變量必須同時滿足三個條件:
1)對變量的寫入操作不依賴其當前值姑子,如number++不可以〔饨或者能夠確保只有單一的線程修改變量的值街佑。
2)該變量沒有包含在具有其他變量的不變式中,如果有多個volatile變量捍靠,則每個volatile變量必須獨立于其他的volatile變量沐旨。
3)訪問變量時,沒有其他的原因需要加鎖榨婆。
synchronized和volatile之間的區(qū)別
- 加鎖可以保證可見性和原子性磁携,但是volatile只能保證可見性。
- volatile不需要加鎖良风,比synchronized更輕量級谊迄,不會阻塞線程,效率更高烟央。所以统诺,如果能用volatile解決問題,還是應盡量使用volatile疑俭。
其他
- 即使沒有保證可見性的措施粮呢,很多時候共享變量也能夠在主內存和工作內存中得到及時的更新?
一般只有在短時間內高并發(fā)的情況下才會出現(xiàn)變量得不到及時更新的情況,因為CPU在執(zhí)行時會很快的刷新緩存啄寡,所以一般情況下很難看到這種問題豪硅,而且也與硬件性能有很大的關系,所以挺物,結果都是不可預測的懒浮。 - java中l(wèi)ong、double是64位的识藤,其讀寫會分成兩次32位的操作嵌溢,并不是原子操作,但很多商用虛擬機都進行了優(yōu)化蹋岩,所以赖草,多線程編程時需要注意這兩種類型值的處理。
參考文章
https://www.cnblogs.com/rocomp/p/4780532.html
Java并發(fā)編程實踐