volatile關鍵字與Java內存模型(JMM)


Java內存模型(JMM)

JMM用來屏蔽不同硬件和操作系統的內存訪問差異,期望Java程序在各種平台上都能實現一致的內存訪問效果;


 
JMM規定里多線程之間的共享變量存儲在主存中, 每個線程單獨擁有一個本地內存( 邏輯概念 ),本地內存存儲線程操作的共享變量副本;
  • JMM中的變量指的是線程共享變量(實例變量,static字段和數組元素),不包括線程私有變量(局部變量和方法參數);
  • JMM規定線程對變量的寫操作都在自己的本地內存對副本進行,不能直接寫主存中的對應變量;
  • 多線程間變量傳遞通過主存完成(Java線程通信通過共享內存),線程修改變量后通過本地內存寫回主存,從主存讀取變量,彼此不允許直接通信(本地內存私有原因);
綜上,JMM通過控制主存和每個線程的本地內存的數據交互,保證 一致的內存 可見性;

JMM特征

原子性:原子性是指多線程一起執行時,一個線程操作開始后不會被其他線程干擾,操作不可被中斷;
  • synchronizd臨界區執行具有原子性;
  • volatile僅僅保證對單個volatile變量的操作具有原子性;
可見性:一個線程修改共享變量時,其他線程能夠立即知道這個修改;
  • 單線程:不存在內存可見性問題;
  • 多線程:Java通過volatile, synchronized, final關鍵字實現可見性;
    • volatilevalatile變量保證變量新值立即被同步回主存,每次讀取valtile變量都立即從主存刷新;
    • synchronized:對變量進行解鎖前,將對應變量同步回內存;
    • final:final字段一旦初始化完畢,並且this引用沒有發生逃逸,其他線程立即看到final字段值;
有序性:線程內操作有序進行,線程間操作有序進行;
  • Java通過volatilesynchronized保證線程間操作的有序性
    • volatile通過禁止重排序實現有序性;
    • synchronized通過聲明臨界區,保證線程互斥訪問,實現有序性;

synchronized與volatile辨析

  • volatile是線程同步的輕量級實現,只用於修飾變量,synchronized用於修飾方法和語句塊;
  • 多線程訪問volatile不會發生阻塞,但是synchronized會發生阻塞;
  • volatile保證數據的可見性,不保證原子性;synchronized保證數據的可見性和原子性;
  • volatile強調共享變量在多線程之間的可見性,synchronized強調多線程訪問資源的同步性;


重排序與happen-before規則

影響多線程有序性:重排序

  • 編譯器重排序:編譯器保證不改變單線程執行結果的前提下,可以調整多線程語句執行順序;
  • 處理器重排序如果不存在數據依賴性,處理器可以改變語句對應機器指令的執行順序;
JMM通過happen-before規則,底層禁止特定類型的編譯器重排序和處理器重排序,保證內存的可見性和有序性

happen-before規則

JMM為所有操作定義了一個偏序關系,稱之為happen-before。 在JMM中,如果A操作對B操作存在happen-before關系;A操作的執行結果全部對B操作可見; 因此不同操作的時間順序和先行發生規則沒有關系,happen-before強調前者修改結果全部對后者可見如果A,B操作不存在happen-before關系,JVM會對它們進行任意重排序;
JMM默認 happen-before 規則:
  • 程序順序規則:一個線程的每個操作,先於該線程其他后續操作執行;
    • 線程的start()方法先於線程內其他方法執行,線程所有操作先於線程的終結操作;
  • 鎖規則:對一個monitor的解鎖必然先於對該monitor的加鎖;
  • volatile變量規則:對volatile的寫操作先於讀操作;
  • 傳遞性:A先於B,B先於C,必然A先於C;

內存屏障指令(memory barriers)

內存屏障指令是一組處理指令,用來限制內存操作的順序;

volatile關鍵字

★ volatile實現原理 

volatile變量寫,匯編指令會多出 Lock前綴,Lock前綴在多核處理器下的作用:
  • 將當前處理器緩存行的數據寫回主存;
  • 令其他CPU里緩存該內存地址的數據無效;
針對編譯器重排序: JMM針對編譯器指定了volatile重排序規則表,規定哪些先后操作不能進行編譯器重排序:
針對處理器重排序:編譯器在生成字節碼指令時,通過在指令序列中插入內存屏障指令來禁止特定類型的處理器重排序,以 實現volatile內存語義 volatile底層通過內存屏障指令實現
  • 在每個volatile變量寫操作之前插入StoreStore屏障,之后插入StoreLoad屏障;
    • 之前插入StoreStore屏障:禁止volatile寫之前的寫操作與其重排序,保證之前的所有寫操作都寫回主存,對volatile寫可見
    • 之后插入StoreLoad屏障:禁止volatile寫之后的讀寫操作與其重排序,實現volatile寫結果對后續操作可見
  • 在每個volatile變量讀操作之后,接連插入LoadLoad屏障,LoadStore屏障;
    • 插入LoadLoad屏障:禁止volatile變量讀之后的讀操作與其重排序;
    • 插入LoadStore屏障:禁止volatile變量讀之后的寫操作與其重排序;
    • 通過插入兩次內存屏障,實現volatile讀結果對后續操作可見
JMM通過上述內存屏障插入策略,保證在任意平台上 volatile 的內存語義一致;

volatile關鍵字語義

volatile用來修飾共享變量(成員變量,static變量)表明:
  • volatile變量:當寫一個volatile變量時,JMM會把所有線程本地內存的對應變量副本刷新回主存;
    • volatile寫和解鎖內存語義相同;
  • volatile變量:當讀一個volatile變量時,JMM會設置該線程的volatile變量副本(本地內存中)無效,線程只能從主存中讀取該變量;
    • 保證了volatile變量讀,總能看見對該volatile變量最后的修改;
    • volatile變量讀和加鎖內存語義相同;
通過上述機制, volatile 保證共享變量一旦被修改,新值對所有線程可見;









 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM