收錄於話題
#進階架構師 | 併發編程專題
12個
點擊上方「java進階架構師」,選擇右上角「置頂公衆號」
20大進階架構專題每日送達
java
前面講解了併發編程的三大核心問題:原子性、可見性、有序性。文章見:【原創】Java併發編程系列03 | 重排序-可見性和有序性問題根源
那麼,做爲從最開始就支持併發的語言,Java是如何解決這些核心問題的呢?程序員
JMM抽象結構模型面試
JMM定義了線程和主內存之間的抽象關係:
線程之間的共享變量存儲在主內存中
每一個線程都有一個私有的本地內存,本地內存中存儲了該線程用以讀/寫共享變量的副本編程
共享變量:堆內存在線程之間共享,存儲在堆內存中全部實例域、靜態域和數組元素都是共享變量數組
Java內存模型
線程之間通訊多線程
線程A與線程B通訊:
線程A把本地內存A中的共享變量刷新到主內存中去。
線程B到主內存中去讀取線程A以前已更新過的共享變量。
從總體來看,這個過程就是線程A在向線程B發送消息。這個通訊過程必需要通過主內存。JMM經過控制主內存與每一個線程的本地內存之間的交互,來爲Java程序員提供內存可見性保證。
舉例:架構
public class JMMTest { static int a = 0;// 主內存中的共享變量 public static void main(String[] args) { new Thread() { public void run() { a = 1;// 線程本地內存中操做共享變量a,並將a=1刷新到豬內存中 while(true) {// 測試用,爲了保持線程運行 } }; }.start(); new Thread() { public void run() { System.out.println(a);// 線程到主內存中讀取變量a while(true) { } }; }.start(); } }
兩個線程之間的通訊過程以下圖:
併發
要求程序員都去搞懂重排序以及JMM內存屏障再去編程是不現實的。
JMM提供了簡單易懂的happens-before原則,並向程序員保證執行併發程序會遵照happens-before原則。
程序員只需理解happens-before原則,按照happens-before原則寫併發代碼,就能保證內存可見性和有序性。
JMM的設計app
1.程序員對內存模型的使用
程序員但願內存模型易於理解、易於編程。程序員但願基於一個強內存模型來編寫代碼。
JMM向程序員提供的happens-before規則,簡單易懂且提供了足夠強的內存可見性保證。程序員能夠把happens-before規則當作強內存模型看待。
2.編譯器和處理器對內存模型的實現
編譯器和處理器但願內存模型對它們的束縛越少越好,這樣它們就能夠作儘量多的優化來提升性能。編譯器和處理器但願實現一個弱內存模型。ide
JMM遵循一個基本原則:只要不改變程序的執行結果(指的是單線程程序和正確同步的多線程程序),編譯器和處理器怎麼優化都行。
例如這些優化既不會改變程序的執行結果,又能提升程序的執行效率。
1.若是編譯器通過細緻的分析後,認定一個鎖只會被單個線程訪問,那麼這個鎖能夠被消除。
2.若是編譯器通過細緻的分析後,認定一個volatile變量只會被單個線程訪問,那麼編譯器能夠把這個volatile變量看成一個普通變量來對待。
如圖,程序員、happens-before、JMM之間的關係:
一個操做執行的結果須要對另外一個操做可見,那麼這兩個操做之間必須存在happens-before關係。
兩個操做能夠是單線程或多線程,happens-before解決的就是多線程內存可見性問題。區分數據依賴性和as-if-seial針對單線程。
happens-before原則定義以下:
1)一個操做happens-before另外一個操做,那麼第一個操做的執行結果將對第二個操做可見,並且第一個操做的執行順序排在第二個操做以前。
2)兩個操做之間存在happens-before關係,並不意味着必定要按照happens-before原則制定的順序來執行。若是重排序以後的執行結果與按照happens-before關係來執行的結果一致,那麼這種重排序並不非法。
happens-before原則規則:
1)程序次序規則:一個線程內,按照代碼順序,書寫在前面的操做先行發生於書寫在後面的操做; 2)鎖定規則:一個unLock操做先行發生於後面對同一個鎖額lock操做; 3)volatile變量規則:對一個變量的寫操做先行發生於後面對這個變量的讀操做; 4)傳遞規則:若是操做A先行發生於操做B,而操做B又先行發生於操做C,則能夠得出操做A先行發生於操做C; 5)線程啓動規則:Thread對象的start()方法先行發生於此線程的每一個一個動做; 6)線程中斷規則:對線程interrupt()方法的調用先行發生於被中斷線程的代碼檢測到中斷事件的發生; 7)線程終結規則:線程中全部的操做都先行發生於線程的終止檢測,咱們能夠經過 Thread.join()方法結束、Thread.isAlive()的返回值手段檢測到線程已經終止執行; 8)對象終結規則:一個對象的初始化完成先行發生於他的finalize()方法的開始;
JMM與原子性問題
Java內存模型只保證了基本讀取和賦值是原子性操做,若是要實現更大範圍操做的原子性,須要經過互斥加鎖synchronized和Lock來實現。
JMM定義了線程和主內存之間的抽象關係,共享變量存儲在主內存中,線程本地內存中存儲了該線程用以讀/寫共享變量的副本。
JMM向程序員提供的happens-before規則來解決可見性和有序性問題。
一個操做執行的結果須要對另外一個操做可見,那麼這兩個操做之間必須存在happens-before關係。
———— e n d ————
微服務、高併發、JVM調優、面試專欄等20大進階架構師專題請關注公衆號【Java進階架構師】後在菜單欄查看。
看到這裏,說明你喜歡本文你的轉發,是對我最大的鼓勵!在看亦是支持↓