Volatile原理分析

volatile 用於提供順序和可見性,volatile 類型變量即便在沒有同步塊的狀況下賦值也不會與其餘語句重排序,volatile 所修飾的變量的修改會馬上寫到主存去,解決了可見性的問題,concurrent 包中大量使用了 volatile 來修飾狀態。

操做系統內存
    計算機在運行程序時,每條指令都是在 CPU 中執行的,在執行過程當中勢必會涉及到數據的讀寫。咱們知道程序運行的數據是存儲在主存中,這時就會有一個問題,讀寫主存中的數據沒有 CPU 中執行指令的速度快,若是任何的交互都須要與主存打交道則會大大影響效率,因此就有了 CPU 高速緩存。CPU 高速緩存爲某個 CPU 獨有,只與在該 CPU 運行的線程有關。
解決緩存一致性方案有兩種:緩存

  • 經過在總線加 LOCK# 鎖的方式。它是採用一種獨佔的方式來實現的,即總線加 LOCK# 鎖的話,只能有一個 CPU 可以運行,其餘 CPU 都得阻塞,效率較爲低下。
  • 經過緩存一致性協議。緩存一致性協議(MESI協議)它確保每一個緩存中使用的共享變量的副本是一致的。其核心思想以下:當某個CPU 在寫數據時,若是發現操做的變量是共享變量,則會通知其餘 CPU 告知該變量的緩存行是無效的,所以其餘 CPU 在讀取該變量時,發現其無效會從新從主存中加載數據。

Java內存模型
    Java 內存模型規定全部的對象都是存在主存當中,每一個線程都有本身的工做內存。線程對變量的全部操做都必須在工做內存中進行,而不能直接對主存進行操做。而且每一個線程不能訪問其餘線程的工做內存。線程執行的時候用到某變量,首先要將變量從主內存拷貝的本身的工做內存空間,而後對變量進行操做:讀取,修改,賦值等,這些均在工做內存完成,操做完成後再將變量寫回主內存。
    Java 提供了 volatile 關鍵字來保證可見性。當一個共享變量被 volatile 修飾時,它會保證修改的值會當即被更新到主存,當有其餘線程須要讀取時,它會去內存中讀取新值。而普通的共享變量不能保證可見性,由於普通共享變量被修改以後,何時被寫入主存是不肯定的,當其餘線程去讀取時,此時內存中可能仍是原來的舊值,所以沒法保證可見性。

volatile實現原理
    volatile 在 JVM 底層是經過內存屏障(memory barrier)實現的。當你寫一個 volatile 變量以前,Java 內存模型會插入一個寫屏障(write barrier);讀一個 volatile 變量以前,會插入一個讀屏障(read barrier)。在你寫一個 volatile 域時,能保證任何線程都能看到你寫的值;在寫以前,也能保證任何數值的更新對全部線程是可見的,由於內存屏障會將其餘全部寫的值更新到緩存。

內存屏障(meomory barrier)
序在運行時內存實際的訪問順序和程序代碼編寫的訪問順序不必定一致,這就是內存亂序訪問。內存亂序訪問行爲出現的理由是爲了提高程序運行時的性能。內存亂序訪問主要發生在兩個階段:多線程

  1. 編譯時,編譯器優化致使內存亂序訪問(指令重排);
  2. 運行時,多 CPU 間交互引發內存亂序訪問。


內存屏障有兩個能力:jvm

  1. 告訴編譯器和 CPU,禁止屏障兩邊的指令重排序;
  2. 強制把寫緩衝區/高速緩存中的髒數據等寫回主內存,讓緩存中相應的數據失效。


volatile 保證 long 和 double 的原子性
    long 和 double 兩種數據類型的操做可分爲高32位和低32位兩部分,對 long 和 double 的操做也是分兩次完成。在64位機上使用64位的 jvm 中 long 和 double 的操做是原子的。因此,官方鼓勵使用 volatile 修飾 long 和 double。

使用場景性能

  1. 標識狀態;
  2. 一個線程寫,多線程讀;
  3. 使用鎖進行全部變化的操做,使用 volatile進行只讀操做。
相關文章
相關標籤/搜索