一、衆所周知,java的內存模型是一個主內存,每一個線程都有一個工做內存空間,那麼主內存同步到工做內存是何時發生的呢?工做內存同步會主內存又是何時發生的呢?java
在cpu進行線程切換時就會發生這些同步嗎?那若是是多核cpu呢,多個核心間沒有線程切換,那麼內存同步是在何時發生的呢?緩存
多個cpu核心共享同一片內存區域,可是cpu的緩存並非共享的,jvm是個虛擬的計算機,把底層抽象化到jvm內部了,那麼jvm內部的內存同步是在何時進行的呢?jvm
二、volatile關鍵的做用到底是什麼?spa
已知:一、保證每次對volatile關鍵字修飾的變量作出的修改,都當即同步到主內存區域中,可是若此時已有其餘線程從主內存區域中獲取過值並放到本身的工做內存中,這個同步回去的值是不能直接反應到已經獲取過這個值的線程中的。線程
二、當線程訪問某一個對象時候值的時候,首先經過對象的引用找到對應在堆內存的變量的值,而後把堆內存變量的具體值load到線程本地內存中,創建一個變量副本,以後線程再也不和對象在堆內存變量值有任何關係,而是直接修改副本變量的值,在修改完以後的某一個時刻(線程退出以前),自動把線程變量副本的值回寫到對象在堆中變量。調試
這個某個時刻在使用volatile修飾的變量上,是當即,其餘的不肯定,應該是在線程切換時。對象
三、書上寫的是確保這個變量在初始化成一個實例時,多個線程正確的處理這個變量。實例是這樣的:內存
假設線程一執行到instance = new SingletonKerriganD()這句,這裏看起來是一句話,但實際上它並非一個原子操做(原子操做的意思就是這條語句要麼就被執行完,要麼就沒有被執行過,不能出現執行了一半這種情形)。事實上高級語言裏面非原子操做有不少,咱們只要看看這句話被編譯後在JVM執行的對應彙編代碼就發現,這句話被編譯成8條彙編指令,大體作了3件事情: 1.給Kerrigan的實例分配內存。 2.初始化Kerrigan的構造器 3.將instance對象指向分配的內存空間(注意到這步instance就非null了)。 get
可是,因爲Java編譯器容許處理器亂序執行(out-of-order),以及JDK1.5以前JMM(Java Memory Medel)中Cache、寄存器到主內存回寫順序的規定,上面的第二點和第三點的順序是沒法保證的,也就是說,執行順序多是1-2-3也多是1-3-2,若是是後者,而且在3執行完畢、2未執行以前,被切換到線程二上,這時候instance由於已經在線程一內執行過了第三點,instance已是非空了,因此線程二直接拿走instance,而後使用,而後瓜熟蒂落地報錯,並且這種難以跟蹤難以重現的錯誤估計調試上一星期都未必能找得出來,真是一茶几的杯具啊。 編譯器
這裏的描述看起來就是在切換線程是主內存與線程內存進行的同步,也就是說volatile還有個做用是防止new了一半被更新到主內存。以及每次使用前都去主內存更新。
三、synchronize關鍵字知道了
在線程進入synchronized塊以前,會把工做存內存中的全部內容映射到主內存上,而後把工做內存清空再從主存儲器上拷貝最新的值。而 在線程退出synchronized塊時,一樣會把工做內存中的值映射到主內存,但此時並不會清空工做內存。這樣一來就能夠強制其按照上面的順序運行,以 保證線程在執行完代碼塊後,工做內存中的值和主內存中的值是一致的,保證了數據的一致性!