現代計算機,cpu在計算的時候,並不老是從內存讀取數據,它的數據讀取順序優先級是:寄存器-高速緩存-內存,線程計算的時候,原始的數據來自內存,在 計算過程當中,有些數據可能被頻繁讀取,這些數據被存儲在寄存器和高速緩存中,當線程計算完後,這些緩存的數據在適當的時候應該寫回內存,當多個線程同時讀 寫某個內存數據時,因爲涉及數據的可見性、操做的有序性,因此就會產生多線程併發問題。java
Java做爲平臺無關性語言,JLS(Java語言規範)定義了一個統一的內存管理模型JMM(Java Memory Model),JMM屏蔽了底層平臺內存管理細節,在多線程環境中必須解決可見性和有序性的問題。JMM規定了jvm有主內存(Main Memory)和工做內存(Working Memory) ,主內存存放程序中全部的類實例、靜態數據等變量,是多個線程共享的,而工做內存存放的是該線程從主內存中拷貝過來的變量以及訪問方法所取得的局部變量, 是每一個線程私有的其餘線程不能訪問,每一個線程對變量的操做都是以先從主內存將其拷貝到工做內存再對其進行操做的方式進行,多個線程之間不能直接互相傳遞數 據通訊,只能經過共享變量來進行。緩存
JLS定義了線程對主存的操做指令:read,load,use,assign,store,write。這些行爲是不可分解的原子操做,在使用上相互依賴,read-load從主內存複製變量到當前工做內存,use-assign執行代碼改變共享變量值,store-write用工做內存數據刷新主存相關內容。安全
線程要引用某變量,若是線程工做內存中沒有該個變量,經過read-load從主內存中拷貝一個副本到工做內存中,完成後線程會引用該副本,當同一個線程 再次引用該變量時,有可能從新從主存中獲取變量副本(read-load-use),也有可能直接引用原來的副本(use),也就是說read、 load、use順序能夠由jvm實現系統決定。多線程
線程要寫入某變量,它會將值指定給工做內存中的變量副本(assign),完成後這個變量副本會同步到主存(store-write),至於什麼時候同步過去,即assign,store,write順序由jvm實現系統決定。併發
多線程對主存的有序性操做有可能會致使併發問題,看一個例子:jvm
public class Test{ public int i = 0; public void add(){ i++; } }
前提:線程a、b使用類Test的同一個實例,執行順序1-6函數
*單線程環境下,i進行兩次加1,結果一定是2,但多線程環境下,i進行兩次加1,結果不必定是2,這取決於上例中第2和第4步的執行順序!this
volatile是java提供的一種同步手段,只不過它是 輕量級的同步,爲何這麼說,由於volatile只能保證多線程的內存可見性,不能保證多線程的執行有序性。而最完全的同步要保證有序性和可見性。當同 一線程屢次重複對字段賦值時,線程有可能只對工做內存中的副本進行賦值,直到最後一次賦值後才同步到主存儲區。任何被volatile修飾的變量,都不拷 貝副本到工做內存,任何修改都及時寫在主存。所以對於valatile修飾的變量的修改,全部線程立刻就能看到,可是volatile不能保證對變量的修 改是有序的。要使 volatile 變量提供理想的線程安全,必須同時知足下面兩個條件:spa
一、對變量的寫操做不依賴於當前值。線程
二、該變量沒有包含在具備其餘變量的不變式中。
錯誤的例子:
public class VolatileFalse{ public volatile int i; public void add(){ i++; } }
說明:雖然volatile 保證對i的修改「及時」寫在主存,全部線程立刻能看到,但i = i + 1 對變量i的寫操做依賴於當前值,而當前值是可變的,因爲多線程下讀寫i的值是無序的,因此多個線程運行VolatileFalse的同一個實例後的到i的 最終值不必定是正確。
正確的例子:
public class VolatileTrue{ public volatile int i; public void setI(int j){ this.i = j; } }
說明:沒有volatie聲明,在多線程環境下,i的值不必定是正確的,由於this.i = j;涉及給i賦值和將i的值同步主存的步驟,這個順序可能被打亂。若是用volatie聲明瞭,讀取主存副本到工做內存和同步i到主存的步驟,至關因而一 個原子操做,所以是線程安全的。
volatile適合這種場景:一個變量被多個線程共享,線程直接給這個變量賦值。這是一種很簡單的同步場景,這時候使用volatile的開銷將會很是小。
synchronized關鍵字做爲多線程併發環境的執行有序性的保證手段之一,若是某個線程訪問一個標識爲synchronized的方法,並對相應變量作操做,那麼根據JLS,JVM的執行步驟以下:
對synchronized的一些總結:
public class Test{ public void method0(){...} public synchronized void method1(){...} public void method2() { synchronized (this){...} } public void method3(SomeObject so) { synchronized(so) {...} } public void method4() { ... private byte[] lock = new byte[0]; synchronized (lock){...} ... } public synchronized static void method5(){...} public void method6(){ synchronized(Test.class){...} } }