先談談硬件是如何工做的,舉個例子,你在window操做系統上須要下載一個遊戲(20M),就須要使用cpu和內存了,在這個過程當中cpu負責計算,好比計算下載進度,統計下載完成一共須要多少時間等,內存爲cpu提供數據的,負責保存遊戲的全部信息,好比遊戲的大小(20M)數據。在這個過程當中,cpu從內存上取遊戲大小這個數據,而後cpu去計算下載進度,把計算出的進度結果再寫到內存,最終呈現到用戶頁面,大概對cpu和內存應該有個大概的認識了吧!看上去下載遊戲這個過程分工明確,沒有問題,但實際上cpu的計算速度比內存的存取速度高了不知道多少個數量級,這個過程cpu很空閒啊(如圖一),cpu你閒着沒事幹那就是浪費資源浪費錢啊,這是個問題,因而人們就想了個辦法,在內存上面加個(高速)緩存,若是是一些經常使用信息,好比遊戲大小這個數據,那就不用在內存取了,直接在緩存上拿(如圖二),而緩存設計的存取速度是很快的,固然價格也更高,若是恰好緩存上有這個遊戲大小數據,這個操做在計算機的世界叫作緩存命中,這樣就解決了cpu很閒的問題。哈哈,仍是舉個簡單例子吧,咱春節買票回家,儘管你的手速很快,可是仍是一票難求,12306官網響應速度慢,沒辦法家仍是要回的,那就找黃牛,雖然價格貴可是能解決你的痛點。這個例子中你,12306系統,黃牛分別對應cpu,內存和緩存,方便你理解。順便說下,這個黃牛其實也是設計模式中的代理。(圖三,圖四)設計模式
瞭解了硬件架構,再來理解Java內存模型(JMM),如魚得水,JMM是根據硬件架構映射出來的,不是真實存在的,硬件模型是物理的,是真實存在的,以下圖所示,若是如今有兩個線程AB須要同時將共享變量c的值加1,最終的程序運行的結果c的值多是3,也多是2。那咱們一塊兒來看看程序執行過程吧,程序初始化,線程AB將拷貝主內存的共享變量c到各自的工做內存,此時工做內存A,工做內存B的初始化值c值都爲1,初始化結束,以下圖所示。這裏能夠把線程A理解成cpu1,線程B理解成cpu2,工做內存理解成高速緩存。這個過程由於工做內存是線程私有的,由於每一個高速緩存是屬於不一樣CPU是不可見的,工做內存A看不見工做內存B的c值爲1,相反工做內存B也看不到工做內存A的c值。緩存
當線程AB同時將共享變量c加1時,若是線程A先獲取時間片,此時工做內存A的c值加1等於2,而後由工做內存A將變量c=2同步到主內存,此時主內存c變量爲2了,線程A執行結束,釋放時間片給線程B,以下圖所示。此時主內存會更新線程B的工做內存B,將c=2告訴線程B,並更新工做內存B的值c=2,此時B獲取時間片,看到工做內存B值是c=2,加1,c=3,線程B將c=3寫到主內存,此時主內存c的值就是3了,線程B執行結束,整個程序結束。其實在這個過程當中,還有一種意外狀況,若是線程A執行結束後,將主內存的c值變爲2,若是主內存c=2尚未同步更新到工做內存B呢?此時問題就來了,線程B獲取時間片後發現本身的工做內存變量c仍是1,而後加1,此時c=2,將c再更新到主內存,此時主內存的值仍是2,主內存再同步c=2的值給線程B已經失去意義了,由於線程所有執行完畢。在這個程序執行過程當中,其實致使線程安全的本質問題是主內存通知不及時才致使發生的,這個案例中由於主內存不能及時將c=2的值更新到線程B的工做內存,致使線程B獲取不到c已經更新爲2了。安全
那問題來了,cpu各自帶着私有緩存,線程帶着各自私有工做內存,數據都靠着主內存來通訊,可是主內存恰恰又不給力啊,通知線程B的工做內存不給力,致使結果c=2或者c=3的,這就是出現了線程安全問題了,這種安全性問題是因爲緩存不可見形成的,因而我開始懷戀單核cpu時代了,可是逃避也解決不了實際問題,因而那些聰明的人們就想既然緩存不一致,那全部緩存都實現統一的協議能夠嗎,下面咱們就簡單聊下緩存一致性協議。多線程
1)總線Lock#鎖。鎖定總線的開銷比較大,在緩存更新內存後,其餘的cpu都會被鎖定住,禁止與內存通訊,這樣開銷就大了。架構
2)MESI協議。這是緩存一致性協議的具體實現,它經過嗅探技術識別哪一個cpu想修改主內存緩存行信息,若是該緩存行是共享的,先將該緩存行刷新到主內存,再設置其餘cpu的高速緩存的緩存行無效,但頻繁的嗅探其餘cpu想修改的共享數據,也會致使總線風暴。jvm
可見性,有序性,原子性是線程安全的三個重要指標。可見性對理解多線程很是很是很是重要!因爲多核硬件架構的問題,cpu高速緩存之間自己是不可見的,必需要實現緩存一致性協議。咱們剛纔上面也說了硬件方面的方案,多線程對共享變量是不可見的,Java方面也提供了兩個關鍵字來保證多線程狀況下共享變量的可見性方案。spa
在JVM手冊中,當多線程寫被volatile修飾的共享變量時,有兩層語義。操作系統
1)該變量當即刷新到主內存。線程
2)使其餘線程的共享變量當即失效。言外之意當其餘線程須要的時候再從主內存取。設計
在上述案例中,若是c爲一個布爾值而且被volatile修飾,那麼當線程AB同時更新共享變量c時,此時c對於工做內存AB是可見的。
在JVM手冊中,synchronized可見性也有兩層語義。
1)在線程加鎖時,必須從主內存獲取值。
2)在線程解鎖時,必須把共享變量刷新到主內存。
這兩句說明了,時刻保持主內存數據最新,當新的線程獲取鎖須要從主內存獲取值。
今天經過可見性的話題,引出了硬件架構,硬件架構由於多cpu高速緩存引出的不可見性問題,從而引出瞭解決可見性的方案,這是基於硬件的。從Java高級語言的角度,引出了基於硬件的映射模型JMM,並給出了jvm要實現可見性用的一些關鍵字。謝謝你們的觀看,我是叫練,邊叫邊練。有疑問和錯誤歡迎留言和指正。