.Java 併發編程的兩個關鍵問題程序員
併發是讓多個線程同時執行,若線程之間是獨立的,那併發實現起來很簡單,各自執行各自的就行;但每每多條線程之間須要共享數據,此時在併發編程過程當中就不可避免要考慮兩個問題:通訊 與 同步。
通訊
通訊是指消息在兩條線程之間傳遞。
既然要傳遞消息,那接收線程 和 發送線程之間必需要有個前後關係,此時就須要用到同步。通訊和同步是相輔相成的。
同步
同步是指,控制多條線程之間的執行次序。編程
2. 通訊的方式2.1 通訊方式的種類多線程
線程之間的通訊一共有兩種方式:共享內存 和 消息傳遞。
共享內存
共享內存指的是多條線程共享同一片內存,發送者將消息寫入內存,接收者從內存中讀取消息,從而實現了消息的傳遞。
但這種方式有個弊端,即須要程序員來控制線程的同步,即線程的執行次序。
這種方式並無真正地實現消息傳遞,只是從結果上來看就像是將消息從一條線程傳遞到了另外一條線程。
消息傳遞
顧名思義,消息傳遞指的是發送線程直接將消息傳遞給接收線程。
因爲執行次序由併發機制完成,所以不須要程序員添加額外的同步機制,但須要聲明消息發送和接收的代碼。
綜上所述:對於共享內存的通訊方式,須要進行顯示的同步,隱式的通訊;
而對於消息傳遞的通訊方式,須要隱式的同步,顯示的通訊。併發
2.2 Java使用的通訊方式函數
Java使用共享內存的方式實現多線程之間的消息傳遞。所以,程序員須要寫額外的代碼用於線程之間的同步。
PS:其實共享內存的方式從實現過程來看,跟消息傳遞一點關係都沒有:一條線程將消息存入共享內存,另外一條線程從共享內存中讀這條消息。
但從結果來看,整個過程就好像是一條消息被從線程A傳遞到了線程B。
這種方式之因此能實現消息傳遞,依託於兩點:操作系統
3. Java多線程的內存模型(簡化版)線程
全部線程都共享一片內存,用於存儲共享變量;
此外,每條線程都有各自的存儲空間,存儲各自的局部變量、方法參數、異常對象。對象
4. volatile是什麼?排序
Java採用共享內存的方式實現消息傳遞,而共享內存須要依託於同步。Java提供了synchronized、volatile關鍵字實現同步。此外volatile關鍵字還擁有一些額外的功能。內存
5. volatile的使用
在成員變量前加上該關鍵字便可。
publicvolatilebooleanflag;
6. volatile的特性
6.1 重排序
重排序是計算機爲了提升程序執行效率而對代碼的執行順序進行調整。你覺得代碼是一行行順序執行的,但實際並不是如此.
若兩行指令之間沒有依賴關係,那麼計算機能夠對他們的順序進行重排序,但若兩行之間的某個變量被volatile修飾後,重排序規則會發生變化。
在如下狀況下,即便兩行代碼之間沒有依賴關係,也不會發生重排序:
volatile讀
volatile寫
6.2 可見性
什麼是內存可見性?
「內存可見性」指的是一條線程修改完一個共享變量後,另外一個線程若訪問這個變量將會訪問到修改後的值。即:一條線程對共享變量的修改,對其餘線程當即可見。
但若是未對共享變量採用同步機制,那麼共享變量的修改不會對其餘線程當即可見。
爲何會出現內存不可見的狀況?
經過上文可知,在Java中每條線程都有各自獨立的存儲空間,此外還有一個全部線程共享的內存空間。
當開啓線程時,系統會將共享內存中的全部共享變量拷貝一份到線程專屬的存儲空間中。接下來該線程在結束前的全部操做都是基於本身的存儲空間進行的。所以,若一條線程改變了一個共享變量,僅僅改變的是這條線程專屬存儲空間中的變量值;此時若其餘線程訪問這個變量,訪問的仍然是先前從共享存儲空間讀出來的值。
然而咱們但願一條線程將某個共享變量修改後,其餘線程能當即訪問到這個最新的值,而不是失效值。
這時就須要同步機制來解決這個問題。
如何確保共享變量的可見性?
要確保全部共享變量對全部線程是可見的,就須要給全部共享變量使用同步。在Java中你能夠選擇將共享變量用同步代碼塊包裹或用volatile修飾共享變量。
爲何volatile能保證共享變量的內存可見性?
volatile修飾了一個成員變量後,這個變量的讀寫就會比普通變量多一些步驟。
經過對volatile變量讀寫的限制,就能保證線程每次讀到的都是最新的值,從而確保了該變量的內存可見性。
volatile變量贈送的附加功能
進行volatile寫操做時,不只會將volatile變量寫入共享內存,系統還會將當前線程專屬空間中的全部共享變量寫入共享內存。
進行volatile讀操做時,系統也會一次性將共享內存中全部共享變量讀入線程專屬空間。
這就意味着,若是普通變量在volatile寫操做以前被修改,那麼在volatile讀操做以後就能正確讀到他們。
可是,在volatile寫操做以後被修改的普通變量 和 在volatile讀操做以前被訪問的普通變量 都不具備內存可見性。
6.3 原子性
什麼是原子性?
原子性指的是一組操做必須一塊兒完成,中途不能被中斷。
volatile能確保long、double讀寫的原子性
在Java中的全部類型中,有long、double類型比較特殊,他們佔據8字節(64比特),其他類型都小於64比特。在32位操做系統中,CPU一次只能讀取/寫入32位的數據,所以對於64位的long、double變量的讀寫會進行兩步。在多線程中,若一條線程只寫入了long型變量的前32位,緊接着另外一條線程讀取了這個只有「一半」的變量,從而就讀到了一個錯誤的數據。
爲了不這種狀況,須要在用volatile修飾long、double型變量。
在內存可見性與原子性上,volatile就至關因而同步的setter和getter函數。但並不具備volatile的重排序規則,同步塊只確保同步塊內部的指令不發生重排序,並不確保同步塊之外的指令的重排序。
PS1:Java中的byte居然是字節,bit纔是比特(位)。
PS2:char和short-2字節、int和float-4字節、long和double-8字節、byte-1字節
QA:在同步塊中調用wait函數是否會破壞原子性?