多線程的實現原理
JMM怎麽解決原子性、可見性、有序性的問題?
在java中提供了一系列和並發處理相關的關鍵字,比如volatile、synchronized、final、juc等,這些就是java內存模型封裝了底層的實現後提供給開發人員使用的關鍵字,在開發多線程代碼的時候,我們可以直接使用synchronized等關鍵詞來控制並發,使得我們不關心底
層的編譯器優化、緩存一致性的問題了,所以在java內存模型中,除了定義了一套規範,還提供了開放的指令在底層進行封裝後,提供給開發人員使用。
原子性:
在java中提供了兩個高級的字節碼指令monitorenter和monitorexit,在java中對應的synchronized來保證代碼塊內的操作是原子性的。
可見性:
在java中的volatile關鍵字提供了一個功能,那就是被其修飾的變量在被修改後可以立即同步到主內存,被其修飾的變量在每次使用之前都從主內存刷新。因此,可以使用volatile來保證多線程操作時變量的可見性。
除了volatile,java中synchronized和final兩個關鍵字也可以實現可見性。
有序性:
在java中,可以使用synchronized和volatile來保證多線程之間操作的有序性。實現方式有所區別:
volatile關鍵字會禁止指令重排。synchronized關鍵字保證同一時刻只允許一條線程操作。
volatile如何保證可見性
Volatile 修飾共享變量,在進行寫操作時會多出一個lock前綴的匯編指令,會觸發總線鎖和緩存鎖,通過緩存一致性來解決可見性問題;
對於聲明了volatile變量進行寫操作,jvm會向處理器發送一條lock前綴的指令,把這個變量所在的緩存行數據寫到系統內存裏,在根據 Mesi緩存一致性協議,來保證多cpu下各個高速緩存的數據一致性。
volatile如何防止指令重排序
指令重排目的是為了提高cpu的利用率以及性能,cpu的亂序執行優化在單核的時代並不影響正確性,但是在多核時代的多線程能夠在不同的 核心上真正實現並行,一旦線程之間共享數據,可能會出現一些不可預料的問題;
我們可以優化屏障和內存屏障來解決這個問題,一個是編譯器的優化亂序和CPU的執行亂序;
從CPU層面來看,什麽是內存屏障?
CPU的亂序執行,本質還是由於多CPU的機器上,每個CPU都有一個cache,當一個特定數據第一次被特定的cpu獲取時,由於CPU緩存中不存在,就會到內存中去獲取,被加載到高速緩存中後就能夠從緩存中快速訪問;當某個CPU在進行寫操作後,要確保其他CPU已經將這個數據從緩存中移除,這樣才能讓其他CPU安全的修改數據。顯然,存在多個cache時,我們必須通過cache一致性協議來避免數據不一致性的問題,而這個通訊的過程會導致亂序訪問的問題,也是運行時的內存亂序的問題。
現在的cpu架構中已經提供了內存屏障的功能,在x86的cpu中,實現了相應的內存屏障。
寫屏障(store barrier)、讀屏障(load barrier) 和全屏障(full barrier)主要作用是:
1、防止指令重排序;
2、保證數據的可見性;
註意:
對於復合的操作,例如i++這種,volatile不能保證原子性。
多線程的實現原理