JMM即Java Memory Model,它定義了主存、工做內存抽象概念,底層對應着CPU寄存器、緩存、硬件內存、CPU指令優化等。
JMM體如今如下幾個方面編程
退不出的循環
先來看一個現象,main線程對run變量的修改對於t線程不可見,致使了t線程沒法中止 :
爲何呢?分析一下 :
1.初始狀態,t線程剛開始從主內存讀取了run的值到工做內存。
2. 由於t線程要頻繁從主內存中讀取run的值,JIT編譯器會將run的值緩存至本身工做內存中的高速緩存中,減小對主存中run的訪問,提升效率
3. 1秒以後,main線程修改了run的值,並同步至主存,而t是從本身工做內存中的高速緩存中讀取這個變量的值,結果永遠是舊值
解決方法
volatile(易變關鍵字)
它能夠用來修飾成員變量和靜態成員變量,它能夠避免線程從本身的工做緩存中查找變量的值,必須到主存中獲取它的值,線程操做volatile變量都是直接操做主存。
可見性 VS 原子性
前面例子體現的實際就是可見性,它保證的是在多個線程之間,一個線程對volatile變量的修改對另外一個線程可見,不能保證原子性,僅用在一個寫線程,多個讀線程的狀況 :
上例從字節碼理解是這樣的 :
比較一下以前咱們將線程安全時舉的例子 :兩個線程一個i++ 一個i–,只能保證看到最新值,不能解決指令交錯
注意
synchronized語句塊既能夠保證代碼塊的原子性,也同時保證代碼塊內變量的可見性。但缺點是synchronized是屬於重量級操做,性能相對更低。
若是在前面示例中的死循環中加入System.out.println()會發現即便不加volatile修飾符,線程t也能正確看到對run變量的修改了,想想爲何?緩存
JVM會在不影響正確性的前提下,能夠調整語句的執行順序 :
能夠看到,至因而先執行i仍是先執行j,對最終的結果不會產生影響。因此,上面代碼真正執行時,既能夠是
也能夠是
這種特性稱之爲指令重排,多線程下指令重排會影響正確性。安全
volatile的底層實現原理是內存屏障,Memory Barrier(Memory Fence)多線程
以上的實現特色是 :併發
字節碼上看不出來volatile指令的效果
happens-before
happens-before規定了對共享變量的寫操做對其它線程的讀操做可見,它是可見性與有序性的一套規則總結,拋開如下happens-before規則,JMM並不能保證一個線程對共享變量的寫,對於其它線程對該共享變量的讀可見app