JMM【Java Memory Model】內存模型 是圍繞着併發場景下如何處理原子性、可見性、有序性來創建的,數組
Java內存模型來直接保證的原子性變量操做包括read、load、assign、use、store和write這六個;若是應用場景須要一個更大範圍的原子性保證(常常會遇到),Java內存模型還提供了lock和 unlock操做來知足這種需求可使用synchronized【底層monitorenter + monitorexit】。多線程
volatile + final + synchronized。併發
volatile和synchronized兩個關鍵字來保證線程之間操做的有序性,volatile關鍵字本 身就包含了禁止指令重排序的語義,而synchronized則是由「一個變量在同一個時刻只容許一條線程對 其進行lock操做」這條規則得到的,這個規則決定了持有同一個鎖的兩個同步塊只能串行地進入。app
synchronized 很萬能能夠保證原子性+有序性+可見性ide
volatile能夠保證可見性+有序性【不能夠保證原子性】優化
happens-before能夠解決不跨線程的有序性,保證了時序性,可是基於Happens-before的內存模型是較弱的,會發生一些違反因果關係的結果【當一個寫操做發生在了一個其依賴的讀操做以前,咱們將這樣的問題稱爲因果關 系,由於它涉及寫操做是否會觸發自身發生的問題。】,可是缺徹底遵循happens-before規則,因此爲了解決happens-before的問題JSR-133有提出了一個synchronized-with來解決。idea
Happens-Before and Synchronizes-With Edges If we have two actions x and y, we use x hb → y to mean that x happens-before y. If x and y are actions of the same thread and x comes before y in program order, then x hb → y.複製代碼
synchronized-with是個發生在兩個不一樣thread間的同步行爲,當A synchronized-with B的時,表明A對內存操做的效果,對於B是可見的。而A和B是兩個不一樣的thread的某個操做。其實synchronized-with就是跨thread版本的happens-before。所以,Java必需要定義一些特殊的語法,像volatile, synchronized, final來確保針對同一個變數的跨thread內存操做可以正確的同步。線程
Mutual Exclusive【互斥性】 對同一個對象而言,不可能有兩個前綴synchronized的方法同時交錯執行,當一個thread正在執行前綴synchronized的方法時,其餘想執行synchronized方法的thread會被擋住。 創建Happens Before關係 對同一個對象而言,當一個thread離開synchronized方法時,會自動對接下來調用synchronized方法的thread創建一個happens-before關係,前一個synchronized的方法對該對象所作的修改,保證對接下來進入synchronized方法的thread可見。【有序性】3d
要確保這件事情,表明JVM必需要作兩件事,一個是在離開synchronized代碼段時,把local processor的cache寫入到內存內,另外一個是在進入下一個synchronized前,要讓local cache失效,使處理器從新去main memory抓正確的值。這樣纔可以確保每次進入synchronized代碼段時,對象的狀態是最新的。【內存可見性】orm
某個管程 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 邊緣。注意,該對象 的全部凍結操做 happen-before 前面那個 happens-before 邊緣 的起始點。
There are two key ideas to understanding whether a program is correctly synchronized:Conflicting Accesses Two accesses (reads of or writes to) the same shared field or array element are said to be conflicting if at least one of the accesses is a write. Happens-Before Relationship Two actions can be ordered by a happens-before relationship. If one action happens-before another, then the first is visible to and ordered before the second. It should be stressed that a happens-before relationship between two actions does not imply that those actions must occur in that order in a Java platform implementation. The happens-before relation mostly stresses orderings between two actions that conflict with each other, and defines when data races take place. There are a number of ways to induce a happens-before ordering, including: • Each action in a thread happens-before every subsequent action in that thread. • An unlock on a monitor happens-before every subsequent lock on that monitor. • A write to a volatile field happens-before every subsequent read of that volatile. • A call to start() on a thread happens-before any actions in the started thread. • All actions in a thread happen-before any other thread successfully returns from a join() on that thread. • If an action a happens-before an action b, and b happens before an action c, then a happensbefore c.複製代碼
理解一個程序是否被正確的同步了,有兩個關鍵概念: 衝突訪問(Conflicting Accesses) 對同一個共享字段或數組元素存在兩個訪問(讀 或寫),且至少有一個訪問是寫操做,就稱做有衝突。 Happens-Before 關係 兩個動做(action)能夠被 happens-before 關係排序。若是一 個動做 happens-before 另外一個動做,則第一個對第二個可見,且第一個排在第二個 以前。必須強調的是,兩個動做之間存在 happens-before 關係並不意味着這些動做 在 Java 中必須以這種順序發生。happens-before 關係主要用於強調兩個有衝突的動 做之間的順序,以及定義數據爭用的發生時機。能夠經過多種方式包含一個 happens-before 順序,包括: 某個線程中的每一個動做都 happens-before 該線程中該動做後面的動做。 某個管程上的 unlock 動做 happens-before 同一個管程上後續的 lock 動做。 對某個 volatile 字段的寫操做 happens-before 每一個後續對該 volatile 字段的讀操做。 在某個線程對象上調用 start()方法 happens-before 該啓動了的線程中的任意動做。 某個線程中的全部動做 happens-before 任意其它線程成功從該線程對象上的 若是某個動做 a happens-before 動做 b,且 b happens-before 動做 c,則有 a happens-before c.複製代碼
public class Singleton { private int i = 0; private static Singleton instance = null; public static Singleton getInstance() { if(null == instance) { instance = new Singleton(); // 1 i++; //2 } return instance; } }複製代碼
不管對於1;或2都會被即便編譯器優化i++ 分爲三步 取i、加1;賦值 ,1分爲 new對象、初始化、關聯引用
多線程狀況下都會有可能被其餘線程獲取到初始化不徹底的對象和變量。
因此須要使用volatile進行內存可見性的保證。
深刻理解Java虛擬機:JVM高級特性與最佳實踐(第3版)