近幾年計算性能經過重排序實現了很大的提高,並且處理器也愈來愈朝着多核處理器發展以實現硬件的並行性。隨着處理器的不斷強大,編譯器也在不斷的改進:經過指令重排序來實現優化執行,使用成熟的全局寄存器分配算法
算法。這些都使得線程在內存內的操做更趨於複雜,若是沒有正確的同步機制下,內存間的操做呈現亂序執行,從而不能保證計算結果的正確性。緩存
編譯器重排序:使得編譯後的指令順序能夠和源代碼的順序不同安全
處理器重排序:在編譯器重排序的基礎上再進行指令的優化重排序多線程
處理器的並行性:多線程內線程之間的操做執行順序不一樣,要實現線程間的數據共享在沒有同步的機制下易產生不安全的數據共享。架構
處理器的多級緩存:若是緩存提交至主內存的順序沒有必定的同步機制下,就會出現提交順序的亂序,使得共享數據在內存的不可見性。app
在指令重排序的狀況下,只要保證最終的計算結果和嚴格的串行執行環境下的結果一致下,重排序等優化措施是能夠的。函數
多線程中線程間各自的操做執行順序不一樣,強行保持程序的串行性,只會增長線程間的調度次數,而頻繁的線程調度會引發不要的上下文操做使得線程的開銷很大也下降了程序的執行速度。在只須要進行數據共享的操做內使性能
用同步機制協調線程間的操做,實現線程間的數據共享(保存線程間短暫的串行性)就可以減小不要的性能開銷。優化
Java內存模型屏蔽了不一樣處理器架構內存模型之間的差別(JVM經過插入內存柵欄來屏蔽JMM與底層平臺內存模型之間的差別)spa
1)進行重排序的條件:操做之間不存在偏序關係和全序關係
2)禁止重排序的方法:鎖,volatile,final
重排序會破外操做之間的偏序關係(Happens-Before),從而產生數據競爭問題
1)簡介:
Java內存模型是經過各類操做來定義的,包括對變量的讀寫操做,監視器的加鎖和釋放操做,以及現場的啓動和合並操做。
Java內存模型實質是爲最終計算結果的正確性,而採起的實現內存操做間保存偏序關係即實現內存操做的有序性,實現內存操做的內存可見性和原子性的內存訪問操做的模型
2)偏序關係(Happens-Before):
偏序關係(Happens-Before)的本質:保證操做之間的內存可見性
保持偏序關係(Happens-Before)的準則:
規則名稱
內容
說明
程序順序規則 程序代碼順序天然保持操做間的偏序關係 監視器鎖規則 在同一個鎖上鎖的釋放確定在鎖的獲取以後 volatile規則 volatile變量的寫操做確定在volatile變量的讀操做以前 線程啓動規則 線程的啓動操做start確定在線程執行操做以前 線程結束規則 線程執行的任何操做確定在線程檢測到該線程結束以前執行,或者從join操做或者調用Thread.isAlive是返回 中斷規則 interrupt中斷操做必須在線程檢測到線程中斷以前執行 終結器規則 對象的構造函數必須在對象的終結器執行以前執行 傳遞性 操做順序的傳遞性
不安全發佈操做的本質就是:對象的發佈操做和對象的訪問操做之間缺少偏序關係(Happens-Before排序)
除了不可變對象外,使用被另一個線程初始化的對象一般都是不安全的,除非對象的發佈操做是在使用該對象的線程開始使用以前執行(即保存對象的發佈操做在對象的加載操做以前也就是兩個操做間保持偏序關係)
安裝發佈對象的本質就是:使用鎖或者volatile關鍵字來禁止操做之間的重排序,從而保持操做之間的偏序關係
初始化的幾種模式:
初始化模式 |
優勢 |
缺點 |
備註 |
synchronized加鎖模式 | 禁止重排序,實現了線程安全的初始化 下降了初始化類或者建立實例的開銷 |
數據競爭嚴重的狀況下太耗性能 增長了訪問被初始化延遲的字段的開銷 |
不推薦使用 |
類初始化加鎖模式 | 在類的靜態初始化過程完成初始化,實現了線程安全的初始化 下降了初始化類或者建立實例的開銷 |
僅適用於在構造時的狀態,對於可變的對象讀寫操做之間仍然須要同步機制。僅限於靜態字段的初始化 增長了訪問被初始化延遲的字段的開銷 |
類初始化期間得到一個鎖,而且每一個線程都至少獲取一次這個鎖以確保這個類已經加載。類初始化(靜態初始化)時類在初始化階段執行,在類型加載後被線程使用以前執行。 |
雙重檢測鎖定模式(DCL) | 下降了因synchronize鎖帶來的性能消耗 下降了初始化類或者建立實例的開銷 |
沒有實現線程安全的初始化 | 錯誤的不安全的延遲初始化方案 |
基於volatile改良後的雙重檢測鎖定模式(VDCL) | 下降了因synchronize鎖帶來的性能消耗,實現了線程安全的初始化 除了可用於靜態字段也能夠用於實例字段 下降了初始化類或者建立實例的開銷 |
增長了訪問被初始化延遲的字段的開銷 |
不管經過類初始化加鎖模式仍是volatile關鍵字都是實現了禁止重排序或者實現操做間的偏序關係從而保證了操做內存的可見性。
初始化安全性只能保證final修飾的值從構造過程完成時開始的可見性,對於非final的值,或者在構造完成後能夠改變的值,必須採用同步來確保可見性