http://www.javashuo.com/article/p-hsugcpqq-hw.htmlhtml
https://www.ibm.com/developerworks/cn/java/j-jtp06197.htmljava
https://blog.csdn.net/dl88250/article/details/5439024程序員
http://www.infoq.com/cn/articles/double-checked-locking-with-delay-initialization編程
能夠被多個線程訪問的變量稱爲共享變量。緩存
多線程場景下討論共享變量纔有意義。多線程
多線程場景能夠是多CPU,也能夠是單個CPU,只很少單個CPU是以線程調度的形式執行的多線程。併發
【CPU從內存讀取數據的速度】和【CPU向內存寫入數據速度】比【CPU執行指令的速度】要慢不少,所以若是任什麼時候候CPU對數據的操做都要經過和內存的交互來進行,會大大下降CPU指令執行的速度。所以在CPU裏面就有了【高速緩存】。性能
程序運行時,會將運算須要的數據從主存複製一份到CPU的高速緩存當中,那麼CPU進行計算時就能夠直接從它的高速緩存讀取數據和向其中寫入數據,當運算結束以後,再將高速緩存中的數據刷新到主存當中。優化
共享變量在多個CPU的高速緩存中存在副本(通常在多線程編程時纔會出現),多個線程同一時間段內對共享變量進行操做,就可能存在緩存一致性問題。spa
一般來講有如下2種解決方法:
1)總線加LOCK#鎖
2)緩存一致性協議
這2種方式都是硬件層面上提供的方式。
通常來講,處理器爲了提升程序運行效率,可能會對輸入代碼進行優化,它不保證程序中各個語句的執行前後順序同代碼中的順序一致,可是它會保證程序最終執行結果和代碼順序執行的結果是一致的。
處理器在進行重排序時會考慮指令之間的數據依賴性,若是一個指令Instruction 2必須用到Instruction 1的結果,那麼處理器會保證Instruction 1會在Instruction 2以前執行。
硬件層面上CPU爲了提高自身執行性能對指令進行了優化和重排序。
多線程場景下CPU指令重排序可能會影響併發程序的執行,致使問題的根本緣由仍是【多線程場景】下【對共享變量操做】的協調上產生了問題。如何協調好?這個要依據具體的業務場景。volatile能夠禁止指令重排序,在須要禁止指令重排序時可使用volatile。
【緩存一致性問題】和【CPU指令重排序】實際上對應的是併發編程中的【可見性】和【有序性】的問題。後面能夠看到,volatile實際上就能夠解決併發編程時【可見性】和【有序性】的問題。
後面會講到,併發編程涉及【原子性】、【可見性】和【有序性】三方面的問題,只有三方面的問題都能解決,才能保證併發程序正確執行。
volatile能夠解決【可見性】和【有序性】的問題,可是沒法解決【原子性】的問題。volatile能解決的問題也決定了volatile的應用場景。
就是要保證對一個事務要進行【完整的】、【不可中斷】的操做。
何爲事務?事務包括一個操做或者多個操做。一個事務所包含的全部操做,要麼所有執行而且執行的過程不會被任何因素打斷,要麼就都不執行。
多個線程的共享變量。當一個線程對共享變量修改時,其餘線程可以當即看獲得修改的值。
緩存一致性問題就致使共享變量的可見性出現問題。也就是說多CPU下各個CPU高速緩存中共享變量的副本不一致,就會致使各個CPU見到的共享變量不一致,這樣共享變量的可見性就出了問題。
有序性:即程序執行的順序按照代碼的前後順序執行。
由於CPU會對指令進行重排序,因此最終的指令執行順序在CPU硬件層面不必定嚴格按照java代碼層面的順序執行。
考慮單線程和多線程兩種情形。單線程下CPU指令重排序可是仍能保證指令邏輯順序的正確性。
可是多線程下,因爲共享變量的存在,可能致使不可預知的、不可預期的、引發混亂的問題。
這就要求程序員理解這些,並採用技術手段保證多線程下程序仍可按照正確的邏輯運行。
要想併發程序正確地執行,必需要保證原子性、可見性以及有序性。只要有一個沒有被保證,就有可能會致使併發程序運行不正確。
一旦一個共享變量(類的成員變量、類的靜態成員變量)被volatile修飾以後,那麼就具有了兩層語義:
1)保證了不一樣線程對這個變量進行操做時的可見性,即一個線程修改了某個變量的值,這新值對其餘線程來講是當即可見的。
2)禁止進行指令重排序。
http://www.javashuo.com/article/p-glkohnan-dq.html
在前面提到volatile關鍵字能禁止指令重排序,因此volatile能在必定程度上保證有序性。
volatile關鍵字禁止指令重排序有兩層意思:
1)當程序執行到volatile變量的讀操做或者寫操做時,在其前面的操做的更改確定所有已經進行,且結果已經對後面的操做可見;在其後面的操做確定尚未進行;
2)在進行指令優化時,不能將在對volatile變量訪問的語句放在其後面執行,也不能把volatile變量後面的語句放到其前面執行。
前面講述了源於volatile關鍵字的一些使用,下面咱們來探討一下volatile到底如何保證可見性和禁止指令重排序的。
下面這段話摘自《深刻理解Java虛擬機》:
「觀察加入volatile關鍵字和沒有加入volatile關鍵字時所生成的彙編代碼發現,加入volatile關鍵字時,會多出一個lock前綴指令」
lock前綴指令實際上至關於一個內存屏障(也成內存柵欄),內存屏障會提供3個功能:
1)它確保指令重排序時不會把其後面的指令排到內存屏障以前的位置,也不會把前面的指令排到內存屏障的後面;即在執行到內存屏障這句指令時,在它前面的操做已經所有完成;禁止指令重排序
2)它會強制將對緩存的修改操做當即寫入主存;
3)若是是寫操做,它會致使其餘CPU中對應的緩存行無效。
http://www.javashuo.com/article/p-bdtfcxtz-du.html
http://www.javashuo.com/article/p-pkrakqzd-dw.html