JVM—内存可见性
原文地址:內存可見性
什么是可見性
- 可見性:一個線程對共享變量值的修改,能夠及時地被其他線程看到
- 共享變量:如果一個變量在多個線程的工作內存中都存在副本,那么這個變量就是這幾個線程的共享變量
Java內存模型(JMM)的介紹
Java內存模型(Java Memory Model)描述了Java程序中各種變量(共享變量)的訪問規則,及在JVM中將變量存儲到內存和從內存中讀取出變量的底層細節
Java內存模型(JMM)兩條規定
JMM中共享變量可見性實現的原理
線程1對共享變量的修改要想被線程2及時看到,必須要經過如下2個步驟:
要實現共享變量的可見性,必須保證兩點
- 線程修改后的共享變量值能夠及時從工作內存中刷新到主內存中
- 其他線程能夠及時把共享變量的最新值從主內存更新到自己的工作內存中
synchronized實現可見性
JMM關于synchronized的兩條規定
線程執行互斥代碼的過程
- 1.獲得互斥鎖
- 2.清空工作內存
- 3.從主內存拷貝變量的最新副本到工作內存
- 4.執行代碼
- 5.將更改后的共享變量的值刷新到主內存中
- 6.釋放互斥鎖
重排序的介紹:代碼書寫的順序與實際執行的順序不同,指令重排序是編譯器或處理器為了提高程序性能而做的優化
as-if-serial的介紹:無論如何重排序,程序執行結果應與代碼順序執行結果一致(Java編譯器,運行時和處理器都會保證Java在單線程下遵循as-if-serial語義)
int num1 = 1; //第1行代碼 int num2 = 2; //第2行 int sum = num1 + num2; //第3行單線程:第1,2行的順序可以重排,但第3行不能,重排序不會給單線程帶來內存可見性問題,多線程中程序交錯執行時,重排序可能會造成內存可見性問題
導致共享變量在線程間不可見的原因及synchronized解決方案
- 1.線程的交叉執行——>原子性(synchronized的同步性)
- 2.重排序結合線程交叉執行——>原子性
- 3.共享變量更新后的值沒有在工作內存與主內存間及時更新——>可見性
volatile實現可見性
volatile如何實現內存的可見性
深入來說:通過加入內存屏障和禁止重排序優化來實現的
通俗地講:volatile變量在每次被線程訪問時,都強迫從主內存中重讀該變量的值,而當該變量發生變化時,又會強迫將最新的值刷新到主內存,這樣任何時刻,不同的線程總能看到該變量的最新值
線程讀寫volatile變量的過程的總結
線程寫volatile變量的過程
- 1.改變線程工作內存中volatile變量副本的值
- 2.將改變后的副本的值從工作內存刷新到主內存
線程讀volatile變量的過程
- 1.從主內存中讀取volatile變量的最新值到線程的工作內存中
- 2.從工作內存中讀取volatile變量的副本
volatile不能保證volatile變量復合操作的原子性
原子性:每次只有一條線程能執行鎖內代碼
private int number = 0; number++;//不是原子操作volatile不能保證volatile變量復合操作的原子性,解決方案如下:
//使用ReentrantLock鎖保證操作的原子性:
private Lock lock = new ReentrantLock();lock.lock(); try{this.number++; }finally{lock.unlock();//釋放鎖 }要在多線程中安全的使用volatile變量,必須同時滿足
總結
final也可以保證內存的可見性
synchronized和volatile比較
- volatile不需要加鎖,比synchronized更輕量級,不會阻塞線程
- 從內存可見性角度講,volatile讀相當于加鎖,volatile寫相當于解鎖
- synchronized既能保證可見性,又能保證原子性,而volatile只能保證可見性,無法保證原子性
即使沒有使用保證可見性的操作,很多時候共享變量都依然可以在主內存和工作內存中得到及時的更新的原因:一般只有在短時間內高并發的情況下才會出現變量得不到及時更新的情況,因為CPU在執行時間時會很快的刷新緩存,所以一般情況下很難看到這種問題
當程序中有一個long或double類型的變量,而且該變量也沒有任何關鍵字修飾,那么此時對64位的long,double變量的讀寫就不是原子操作,即可以分解。因為java內存模型允許JVM將沒有被volatile修飾的64位數據類型的讀寫操作劃分為兩次32位的讀寫操作來進行,就可能導致多線程并發訪問該變量時,出現只讀了一半就被搶走資源進而導致數據異常的問題,解決方法是在long,double類型的變量上加入volatile關鍵字不過大部分虛擬機已經把long,double類型的變量對其進行一些保護,因此在實際編程中也不需刻意為long,double類型的數據加上volatile關鍵字
超強干貨來襲 云風專訪:近40年碼齡,通宵達旦的技術人生總結
- 上一篇: 分布式服务常见问题—分布式事务
- 下一篇: JVM—内存模型JMM