原子性:一個或多個操做要麼所有執行成功要麼所有執行失敗;java
可見性:當多個線程訪問同一個變量時,若是其中一個線程對其做了修改,其餘線程能當即獲取到最新的值;編程
有序性:程序執行的順序按照代碼的前後順序執行(處理器可能會對指令進行重排序);緩存
CPU愈來愈快,主存逐漸跟不上cpu的頻率,cpu須要等待主存浪費資源。因此cache的出現主要爲了解決cpu和內存之間頻率不匹配的問題。多線程
但cache也帶來了新的問題,併發處理的不一樣步(不過能夠經過總線鎖和緩存一致性來解決)。併發
在執行程序時,爲了提升性能,編譯器和處理器經常會對指令作重排序。app
一、編譯器優化的重排序:編譯器在不改變單線程程序語義的前提下,能夠從新安排語句的執行順序。jvm
二、指令級並行的重排序:現代處理器採用了指令級並行技術(Instruction-Level Parallelism,ILP)來將多條指令重疊執行。性能
若是不存在數據依賴性,處理器能夠改變語句對應機器指令的執行順序。優化
三、內存系統的重排序:因爲處理器使用緩存和讀/寫緩衝區,這使得加載和存儲操做看上去多是在亂序執行。以下圖:spa
as-if-serial語義的意思是:無論怎麼重排序(編譯器和處理器爲了提升並行度),單線程程序的執行結果不能被改變。
編譯器、runtime和處理器都必須遵照as-if-serial語義。
爲了遵照as-if-serial語義,編譯器和處理器不會對存在數據依賴關係的操做作重排序。數據依賴關係以下圖所示:
單線程狀況下,控制依賴關係的重排序,不影響最終結果。多線程狀況下,則可能會破壞程序的意圖。
一、對於編譯器,JMM的編譯器重排序規則會禁止特定類型的編譯器重排序(不是全部的編譯器重排序都要禁止)。
二、對於處理器重排序,JMM的處理器重排序規則會要求Java編譯器在生成指令序列時,插入特定類型的內存屏障(Memory Barriers,Intel稱之爲MemoryFence)指令,
經過內存屏障指令來禁止特定類型的處理器重排序。
一、在每一個volatile寫操做前插入StroreStore屏障
二、在每一個volatile寫操做前插入StroreLoad屏障
三、在每一個volatile讀操做前插入LoadLoad屏障
四、在每一個volatile讀操做前插入LoadStore屏障
volatile
是輕量級的synchronized
,它在多處理器開發中保證了共享變量的「可見性」。volatile
不會引發上下文的切換和調度(禁止指令重排),執行開銷更小。
可見性:對一個volatile
變量的讀,老是能看到(任意線程)對這個volatile
變量最後的寫入。
原子性:對任意單個volatile
變量的讀/寫具備原子性,但相似於volatile++
這種複合操做不具備原子性。
1. 使用volitate修飾的變量在彙編階段,會多出一條lock前綴指令(ACC_VOLATILE)
2. 它確保指令重排序時不會把其後面的指令排到內存屏障以前的位置,也不會把前面的指令排到內存屏障的後面;
即在執行到內存屏障這句指令時,在它前面的操做已經所有完成
3. 它會強制將對緩存的修改操做當即寫入主存
4. 若是是寫操做,它會致使其餘CPU裏緩存了該內存地址的數據無效
volatile的寫-讀與鎖的釋放-獲取有相同的內存效果。
這裏A線程寫一個volatile變量後,B線程讀同一個volatile變量。
A線程在寫volatile變量以前全部可見的共享變量,在B線程讀同一個volatile變量後,將當即變得對B線程可見。
當寫一個volatile變量時,JMM會把該線程對應的本地內存中的共享變量值刷新到主內存。
當讀一個volatile變量時,JMM會把該線程對應的本地內存置爲無效。線程接下來將從主內存中讀取共享變量。
注:關於volatile變量重排序,嚴格限制編譯器和處理器對volatile變量與普通變量的重排序,確保volatile的寫-讀和鎖的釋放-獲取具備相同的內存語義。
JMM重排序分爲編譯器重排序和處理器重排序,JMM會限制這兩種類型的重排序類型來保證volatile的內存語義
一、第二個操做是volatile寫時,第一個操做不論是什麼,都不能重排序
二、第一個操做是volatile讀時,第二個操做不論是什麼,都不能重排序
三、第一個操做是volatile是寫,第二個操做是volatile是讀,不能重排序
volatile本質是在告訴jvm當前變量在寄存器(工做內存)中的值是不肯定的,須要從主存中讀取;
synchronized則是鎖定當前變量,只有當前線程能夠訪問該變量,其餘線程被阻塞住。
volatile僅能使用在變量級別;synchronized則可使用在變量、方法、和類級別的
volatile僅能實現變量的修改可見性,不能保證原子性;而synchronized則能夠保證變量的修改可見性和原子性
volatile不會形成線程的阻塞;synchronized可能會形成線程的阻塞。
volatile標記的變量不會被編譯器優化;synchronized標記的變量能夠被編譯器優化
synchronized 是JVM實現的一種鎖,其中鎖的獲取和釋放分別是monitorenter 和 monitorexit 指令,該鎖在實現上分爲了偏向鎖、輕量級鎖和重量級鎖,
其中偏向鎖在 java1.6 是默認開啓的,輕量級鎖在多線程競爭的狀況下會膨脹成重量級鎖,有關鎖的數據都保存在對象頭中。
對於普通同步方法,鎖是當前實例對象;
對於靜態同步方法,鎖是當前類Class對象;
對於同步方法塊,鎖是Synchronized括號裏配置的對象;
加了 synchronized 關鍵字的代碼段,生成的字節碼文件會多出 monitorenter 和 monitorexit 兩條指令(利用javap -v *.class字節碼文件)
加了 synchronized 關鍵字的方法,生成的字節碼文件中會多一個 ACC_SYNCHRONIZED 標誌位,當方法調用時,調用指令將會檢查方法的 ACC_SYNCHRONIZED 訪問標誌是否被設置,
若是設置了,執行線程將先獲取monitor,獲取成功以後才能執行方法體,方法執行完後再釋放monitor。在方法執行期間,其餘任何線程都沒法再得到同一個monitor對象。
其實本質上沒有區別,只是方法的同步是一種隱式的方式來實現,無需經過字節碼來完成。
一、會讓沒有獲得鎖的資源進入Block狀態,爭奪到資源以後又轉爲Running狀態,這個過程涉及到操做系統用戶模式和內核模式的切換,代價比較高。
二、Java1.6爲 synchronized 作了優化,增長了從偏向鎖到輕量級鎖再到重量級鎖的過分,可是在最終轉變爲重量級鎖以後,性能仍然較低。
JMM會把該線程對應的本地內存置爲無效。從而使得被監視器保護的臨界區代碼必須從主內存中讀取共享變量。
鎖釋放與volatile寫有相同的內存語義;鎖獲取與volatile讀有相同的內存語義。
線程A釋放一個鎖,實質上是線程A向接下來將要獲取這個鎖的某個線程發出了(線程A對共享變量所作修改的)消息。
線程B獲取一個鎖,實質上是線程B接收了以前某個線程發出的(在釋放這個鎖以前對共享變量所作修改的)消息。
線程A釋放鎖,隨後線程B獲取這個鎖,這個過程實質上是線程A經過主內存向線程B發送消息。