并發是讓多個線程同時履行,若線程之間是獨立的,那并發實現起來很簡單,各自履行各自的就行;但常常多條線程之間需要同享數據,此時在并發編程進程中就不可避免要斟酌兩個問題:通訊 與 同步。
通訊
通訊是指消息在兩條線程之間傳遞。
既然要傳遞消息,那接收線程 和 發送線程之間必須要有個前后關系,此時就需要用到同步。通訊和同步是相輔相成的。
同步
同步是指,控制多條線程之間的履行次序。
線程之間的通訊1共有兩種方式:同享內存 和 消息傳遞。
這類方式并沒有真正地實現消息傳遞,只是從結果上來看就像是將消息從1條線程傳遞到了另外一條線程。
綜上所述:對同享內存的通訊方式,需要進行顯示的同步,隱式的通訊;
而對消息傳遞的通訊方式,需要隱式的同步,顯示的通訊。
Java使用同享內存的方式實現多線程之間的消息傳遞。因此,程序員需要寫額外的代碼用于線程之間的同步。
PS:其實同享內存的方式從實現進程來看,跟消息傳遞1點關系都沒有:1條線程將消息存入同享內存,另外一條線程從同享內存中讀這條消息。
但從結果來看,全部進程就好像是1條消息被從線程A傳遞到了線程B。
這類方式之所以能實現消息傳遞,依托于兩點:
所有線程都同享1片內存,用于存儲同享變量;
另外,每條線程都有各自的存儲空間,存儲各自的局部變量、方法參數、異常對象。
Java采取同享內存的方式實現消息傳遞,而同享內存需要依托于同步。Java提供了synchronized、volatile關鍵字實現同步。另外volatile關鍵字還具有1些額外的功能。
在成員變量前加上該關鍵字便可。
public volatile boolean flag;
重排序是計算機為了提高程序履行效力而對代碼的履行順序進行調劑。你以為代碼是1行行順序履行的,但實際并不是如此,重排序詳解請移步至:Java并發編程的藝術(2)——重排序
若兩行指令之間沒有依賴關系,那末計算機可以對他們的順序進行重排序,但如果兩行之間的某個變量被volatile修飾后,重排序規則會產生變化。
在以下情況下,即便兩行代碼之間沒有依賴關系,也不會產生重排序:
volatile讀
volatile寫
“內存可見性”指的是1條線程修改完1個同享變量后,另外一個線程若訪問這個變量將會訪問到修改后的值。即:1條線程對同享變量的修改,對其他線程立便可見。
但如果未對同享變量采取同步機制,那末同享變量的修改不會對其他線程立便可見。
通過上文可知,在Java中每條線程都有各自獨立的存儲空間,另外還有1個所有線程同享的內存空間。
當開啟線程時,系統會將同享內存中的所有同享變量拷貝1份到線程專屬的存儲空間中。接下來該線程在結束前的所有操作都是基于自己的存儲空間進行的。因此,若1條線程改變了1個同享變量,僅僅改變的是這條線程專屬存儲空間中的變量值;此時若其他線程訪問這個變量,訪問的依然是先前從同享存儲空間讀出來的值。
但是我們希望1條線程將某個同享變量修改后,其他線程能立即訪問到這個最新的值,而不是失效值。
這時候就需要同步機制來解決這個問題。
要確保所有同享變量對所有線程是可見的,就需要給所有同享變量使用同步。在Java中你可以選擇將同享變量用同步代碼塊包裹或用volatile修飾同享變量。
volatile修飾了1個成員變量后,這個變量的讀寫就會比普通變量多1些步驟。
volatile變量寫
當被volatile修飾的變量進行寫操作時,這個變量將會被直接寫入同享內存,而非線程的專屬存儲空間。
volatile變量讀
當讀取1個被volatile修飾的變量時,會直接從同享內存中讀,而非線程專屬的存儲空間中讀。
通過對volatile變量讀寫的限制,就可以保證線程每次讀到的都是最新的值,從而確保了該變量的內存可見性。
進行volatile寫操作時,不但會將volatile變量寫入同享內存,系統還會將當前線程專屬空間中的所有同享變量寫入同享內存。
進行volatile讀操作時,系統也會1次性將同享內存中所有同享變量讀入線程專屬空間。
這就意味著,如果普通變量在volatile寫操作之前被修改,那末在volatile讀操作以后就可以正確讀到他們。
但是,在volatile寫操作以后被修改的普通變量 和 在volatile讀操作之前被訪問的普通變量 都不具有內存可見性。
原子性指的是1組操作必須1起完成,中途不能被中斷。
在Java中的所有類型中,有long、double類型比較特殊,他們占據8字節(64比特),其余類型都小于64比特。在32位操作系統中,CPU1次只能讀取/寫入32位的數據,因此對64位的long、double變量的讀寫會進行兩步。在多線程中,若1條線程只寫入了long型變量的前32位,緊接著另外一條線程讀取了這個只有“1半”的變量,從而就讀到了1個毛病的數據。
為了不這類情況,需要在用volatile修飾long、double型變量。
在內存可見性與原子性上,volatile就相當因而同步的setter和getter函數。但其實不具有volatile的重排序規則,同步塊只確保同步塊內部的指令不產生重排序,其實不確保同步塊之外的指令的重排序。
PS1:Java中的byte居然是字節,bit才是比特(位)。
PS2:char和short⑵字節、int和float⑷字節、long和double⑻字節、byte⑴字節