深刻理解 Volatile 的實現原理

Volatile 的官方定義

Java 語言規範第三版中對 volatile 的定義以下: java 編程語言容許線程訪問共享變量,爲了確保共享變量能被準確和一致的更新,線程應該確保經過排他鎖單獨得到這個變量。Java 語言提供了 volatile,在某些狀況下比鎖更加方便。若是一個字段被聲明成 volatile,java 線程內存模型確保全部線程看到這個變量的值是一致的。java

什麼狀況下可以使用 volatile

  1. 在多線程併發編程時,爲了保持共享變量在多個線程的一致性。 =》 可見性
  2. 爲了保證代碼執行按編碼的順序執行。 =》 有序性

併發編程中的三個特性:原子性,有序性和可見性。volatile就做用了其中的兩個。編程

爲何使用 volatile

恰當的使用,它的使用和執行成本比synchronized更低,由於不會引發線程上下文的切換和調度。緩存

volatile的實現原理是什麼

volatile 是依賴於硬件層面的支持,即須要 CPU 的指定來實現。多線程

對於volatile修飾的變量,在彙編語言層面會多一行指令 0x01a3de24: lock addl $0x0,(%esp);。而該lock指令經過查IA-32架構可知主要作兩件事 :架構

  1. 將當前處理器緩存行的數據寫回到系統內存中。
  2. 該寫回內存操做會引發其餘CPU裏緩存了該內存地址的數據失效。 =》 CPU 的嗅探機制。

詳細原理以下:併發

處理器爲了提升處理速度,不直接和內存進行通信,而是先將系統內存的數據讀到內部緩存(L1,L2 或其餘)後再進行操做,但操做完以後不知道什麼時候會寫到內存,若是對聲明瞭 Volatile 變量進行寫操做,JVM 就會向處理器發送一條 Lock 前綴的指令,將這個變量所在緩存行的數據寫回到系統內存。編程語言

可是就算寫回到內存,若是其餘處理器緩存的值仍是舊的,再執行計算操做就會有問題,因此在多處理器下,爲了保證各個處理器的緩存是一致的,就會實現緩存一致性協議,每一個處理器經過嗅探在總線上傳播的數據來檢查本身緩存的值是否是過時了,當處理器發現本身緩存行對應的內存地址被修改,就會將當前處理器的緩存行設置成無效狀態,當處理器要對這個數據進行修改操做的時候,會強制從新從系統內存裏把數據讀處處理器緩存裏高併發

LOCK 前綴指令的改進

Lock 前綴指令會引發處理器緩存回寫到內存。 Lock 前綴指令致使在執行指令期間,聲言處理器的 LOCK# 信號,在該信號期間,會獨佔使用任何共享內存性能

  1. 第一階段:優化

    1. LOCK指令會鎖住總線,致使其餘的處理器不能訪問總線,也就不能訪問系統內存。將多線程的併發變成了串行執行。
  2. 優化後

    1. LOCK指令再也不鎖總線,而是鎖緩存行。並將數據會寫到該緩存,使用緩存一致性來保證原子性

緩存一致性機制會阻止同時修改被兩個以上處理器緩存的內存區域數據。一個處理器的緩存回寫到內存會致使其餘處理器的緩存無效。

新的 CPU 會使用 MESI(修改,獨佔,共享,無效)控制協議來維護內部緩存和其餘處理器的緩存的一致性。

能夠看出硬件技術的進步對於軟件的性能提高有質的飛越。

volatile在軟件層面的優化

併發編程大師 Doug lea在 JDK1.7 中新增了隊列集合類 LinkedTransferQueue,在使用Volatile時用追價字節的方式優化隊列出棧和入棧的性能。

爲何追加 64 字節可以提升併發編程的效率呢?

由於對於英特爾酷睿 i7,酷睿, Atom 和 NetBurst, Core Solo 和 Pentium M 處理器的 L1,L2 或 L3 緩存的高速緩存行是 64 個字節寬,不支持部分填充緩存行,這意味着若是隊列的頭節點和尾節點都不足 64 字節的話,處理器會將它們都讀到同一個高速緩存行中,在多處理器下每一個處理器都會緩存一樣的頭尾節點,當一個處理器試圖修改頭接點時會將整個緩存行鎖定,那麼在緩存一致性機制的做用下,會致使其餘處理器不能訪問本身高速緩存中的尾節點,而隊列的入隊和出隊操做是須要不停修改頭接點和尾節點,因此在多處理器的狀況下將會嚴重影響到隊列的入隊和出隊效率。Doug lea 使用追加到 64 字節的方式來填滿高速緩衝區的緩存行,避免頭接點和尾節點加載到同一個緩存行,使得頭尾節點在修改時不會互相鎖定。

上一段話核心意思是: 隊列A的尾節點和隊列B的頭節點 在同一緩存行,隊列 B 修改頭節點時會鎖住整個緩存行,致使隊列A 不能訪問本身的尾節點。所以須要補全 64 字節,讓尾節點獨佔一個緩存行。

該方式是對空間和性能的一個折中和取巧方案。若是併發較大,修改比較頻繁,可使用該方式。主要是爲了不相互鎖定

那什麼狀況下不適合呢?

  1. 緩存行非 64 字節寬的處理器。比較老一些的處理器,如 P6 和奔騰處理器的緩存行是 32 字節寬。
  2. 共享變量不會被頻繁的寫,由於使用追加字節的方式須要處理器讀取更多的字節到高速緩衝區,這自己就會帶來必定的性能消耗,共享變量若是不被頻繁寫的話,鎖的概率也很是小,就不必經過追加字節的方式來避免相互鎖定。

感想

越底層的知識越基礎越重要。CPU 和內存,磁盤的交互機制不瞭解,就不能很好的在軟件層面利用硬件能力進行性能提高。

附上執行指令和 volatile 執行原理圖

CPU 的執行指令

volatile 執行原理圖

參考文獻

https://www.infoq.cn/article/...
相關文章
相關標籤/搜索