volatile變量用來實現併發過程當中遇到的讀寫原子性的實現,不一樣線程對申明volatile變量的寫後,其餘線程對該變量的讀爲最新的值,便可見性。可是volatile不保證複合操做(如i++相似的依賴原值得操做)的線程安全。在JSR133中定義了happends-before原則,對volatile變量的寫happends-before於對該變量的讀。volatile相對於synchronized實現鎖的同步是屬於輕量級的同步。java
參考資料:編程
Java內存模型沒有具體講述前面討論的執行策略是由編譯器,CPU,緩存控制器仍是其它機制促成的。甚至沒有用開發人員所熟悉的類,對象及方法來討論。取而代之,Java內存模型中僅僅定義了線程和內存之間那種抽象的關係。衆所周知,每一個線程都擁有本身的工做存儲單元(緩存和寄存器的抽象)來存儲線程當前使用的變量的值。Java內存模型僅僅保證了代碼指令與變量操做的有序性,大多數規則都只是指出何時變量值應該在內存和線程工做內存之間傳輸。這些規則主要是爲了解決以下三個相互牽連的問題:緩存
- 原子性:哪些指令必須是不可分割的。在Java內存模型中,這些規則需聲明僅適用於-—實例變量和靜態變量,也包括數組元素,但不包括方法中的局部變量-—的內存單元的簡單讀寫操做。
- 可見性:在哪些狀況下,一個線程執行的結果對另外一個線程是可見的。這裏須要關心的結果有,寫入的字段以及讀取這個字段所看到的值。
- 有序性:在什麼狀況下,某個線程的操做結果對其它線程來看是無序的。最主要的亂序執行問題主要表如今讀寫操做和賦值語句的相互執行順序上。
爲何要使用Volatile架構
Volatile變量修飾符若是使用恰當的話,它比synchronized的使用和執行成本會更低,由於它不會引發線程上下文的切換和調度。併發
Volatile的使用優化app
著名的Java併發編程大師Doug lea在JDK7的併發包裏新增一個隊列集合類LinkedTransferQueue,他在使用Volatile變量時,用一種追加字節的方式來優化隊列出隊和入隊的性能。高併發
追加字節能優化性能?這種方式看起來很神奇,但若是深刻理解處理器架構就能理解其中的奧祕。讓咱們先來看看LinkedTransferQueue這個類,它使用一個內部類類型來定義隊列的頭隊列(Head)和尾節點(tail),而這個內部類PaddedAtomicReference相對於父類AtomicReference只作了一件事情,就將共享變量追加到64字節。咱們能夠來計算下,一個對象的引用佔4個字節,它追加了15個變量共佔60個字節,再加上父類的Value變量,一共64個字節。性能
/** head of the queue */ private transient final PaddedAtomicReference<QNode> head; /** tail of the queue */ private transient final PaddedAtomicReference<QNode> tail; static final class PaddedAtomicReference <T> extends AtomicReference <T> { // enough padding for 64bytes with 4byte refs Object p0, p1, p2, p3, p4, p5, p6, p7, p8, p9, pa, pb, pc, pd, pe; PaddedAtomicReference(T r) { super(r); } } public class AtomicReference <V> implements java.io.Serializable { private volatile V value; //省略其餘代碼 }爲何追加64字節可以提升併發編程的效率呢? 由於對於英特爾酷睿i7,酷睿, Atom和NetBurst, Core Solo和Pentium M處理器的L1,L2或L3緩存的高速緩存行是64個字節寬,不支持部分填充緩存行,這意味着若是隊列的頭節點和尾節點都不足64字節的話,處理器會將它們都讀到同一個高速緩存行中,在多處理器下每一個處理器都會緩存一樣的頭尾節點,當一個處理器試圖修改頭接點時會將整個緩存行鎖定,那麼在緩存一致性機制的做用下,會致使其餘處理器不能訪問本身高速緩存中的尾節點,而隊列的入隊和出隊操做是須要不停修改頭接點和尾節點,因此在多處理器的狀況下將會嚴重影響到隊列的入隊和出隊效率。Doug lea使用追加到64字節的方式來填滿高速緩衝區的緩存行,避免頭接點和尾節點加載到同一個緩存行,使得頭尾節點在修改時不會互相鎖定。
那麼是否是在使用Volatile變量時都應該追加到64字節呢?不是的。在兩種場景下不該該使用這種方式。第一:緩存行非64字節寬的處理器,如P6系列和奔騰處理器,它們的L1和L2高速緩存行是32個字節寬。第二:共享變量不會被頻繁的寫。由於使用追加字節的方式須要處理器讀取更多的字節到高速緩衝區,這自己就會帶來必定的性能消耗,共享變量若是不被頻繁寫的話,鎖的概率也很是小,就不必經過追加字節的方式來避免相互鎖定。
它們的定義以下:
某個管程 m 上的解鎖動做 synchronizes-with 全部後續在 m 上的鎖定動做 (這裏的後續是根據同步順序定義的)。
對 volatile 變量 v 的寫操做 synchronizes-with 全部後續任意線程對 v 的讀操 做(這裏的後續是根據同步順序定義的)。
用於啓動一個線程的動做 synchronizes-with 該新啓動線程中的第一個動做。
線程 T1 的最後一個動做 synchronizes-with 線程 T2 中任一用於探測 T1 是否 終止的動做。T2 可能經過調用 T1.isAlive()或者在 T1 上執行一個 join 動做 來達到這個目的。
若是線程 T1 中斷了線程 T2,T1 的中斷操做 synchronizes-with 任意時刻任 何其它線程(包括 T2)用於肯定 T2 是否被中斷的操做。這能夠經過拋出 一個 InterruptedException 或調用 Thread.interrupted 與 Thread.isInterrupted 來實現。
爲每一個變量寫默認值(0,false 或 null)的動做 synchronizes-with 每一個線程 中的第一個動做。 雖然在對象分配以前就爲該對象中的變量寫入默認值看起來有些奇怪,從 概念上看,程序啓動建立對象時都帶有默認的初始值。所以,任何對象的 默認初始化操做 happens-before 程序中的任意其它動做(除了寫默認值的 操做)。
調用對象的終結方法時,會隱式的讀取該對象的引用。從一個對象的構造 器末尾到該引用的讀取之間存在一個 happens-before 邊緣。注意,該對象 的全部凍結操做(見 9.2 節)happen-before 前面那個 happens-before 邊緣 的起始點。