線程間如何通訊以及如何同步?java
通訊是指線程之間以何種機制來交換信息。在命令式編程中,線程之間的通訊機制有兩種:共享內存和消息傳遞。程序員
共享內存編程
在共享內存的併發模型裏,線程之間共享程序的公共狀態,經過寫-讀內存中的公共狀態進行隱式通訊。數組
線程之間沒有公共狀態,線程之間必須經過發送消息來顯式進行通訊。緩存
同步是指程序中用於控制不一樣線程間操做發生相對順序的機制。多線程
有點拗口,個人理解就是多線程之間存在併發問題,線程之間很難保持順序,就像車道上的汽車,有些快有些慢,同步就是由多車道變爲單車道,使線程並行執行改成串行執行,串行後能保證線程的順序。併發
在共享內存併發模型裏,同步是顯式進行的。程序員必須顯式指定某個方法或某段代碼須要在線程之間互斥執行。app
就是使用synchronized或者ReentrantLock。性能
在消息傳遞的併發模型裏,因爲消息的發送必須在消息的接收以前,所以同步是隱式進行的。優化
Java的併發採用的是共享內存模型,Java線程之間的通訊老是隱式進行,整個通訊過程對程序員徹底透明。
java的堆內存是線程共享的。堆內存中存放着類的實例域、靜態域和數組元素。
在方法中執行User user = new User()
圖不必定正確,只是按目前的理解畫的,要看完《深刻了解Java虛擬機》才能對內存模型有更深刻的理解。
局部變量(Local Variables),方法定義參數(Java語言規範稱之爲Formal Method Parameters)和異常處理器參數(Exception Handler Parameters)不會在線程之間共享,它們不會有內存可見性問題,也不受內存模型的影響。
Java線程之間的通訊由Java內存模型本文簡稱爲JMM)控制,JMM決定一個線程對共享變量的寫入什麼時候對另外一個線程可見。
線程之間的共享變量存儲在主內存(Main Memory)中,每一個線程都有一個私有的本地內存(Local Memory),本地內存中存儲了該線程以讀/寫共享變量的副本。
抽象結構示意圖(出自《Java併發編程藝術》)
線程A與線程B通訊:
1)線程A把本地內存A中更新過的共享變量刷新到主內存中去。
2)線程B到主內存中去讀取線程A以前已更新過的共享變量。
線程A與線程B都會在各自的本地內存中緩存一份主內存中的共享變量副本,線程A修改共享變量的值,線程B會感知到主內存中的共享變量發生變化,從而將修改後的值從新讀取到線程B的本地內存中達到線程通訊的目的。兩個步驟實質上是線程A在向線程B發送消息,並且這個通訊過程必需要通過主內存。JMM經過控制主內存與每一個線程的本地內存之間的交互,來爲Java程序員提供內存可見性保證。
重排序是指編譯器和處理器爲了優化程序性能而對指令序列進行從新排序的一種手段,重排序分3種類型。
1)編譯器優化重排序:
在不改變單線程程序語義的前提下,能夠從新安排語句的執行順序。
2)指令級並行的重排序:
現代處理器採用了指令級並行技術(Instruction-LevelParallelism,ILP)來將多條指令重疊執行。若是不存在數據依賴性,處理器能夠改變語句對應機器指令的執行順序。
3)內存系統的重排序:
因爲處理器使用緩存和讀/寫緩衝區,這使得加載和存儲操做看上去多是在亂序執行。
Java編譯器在生成指令序列時,插入特定類型的內存屏障(Memory Barriers,Intel稱之爲Memory Fence)指令,經過內存屏障指令來禁止特定類型的處理器重排序。
從源碼到最終執行的指令序列的示意圖(Java併發編程藝術)
上述的1屬於編譯器重排序,2和3屬於處理器重排序。重排序可能會致使多線程程序出現內存可見性問題。對於處理器重排序,JMM的處理器重排序規則會要求Java編譯器在生成指令序列時,插入特定類型的內存屏障指令,經過內存屏障指令來禁止特定類型的處理器重排序。
JMM屬於語言級的內存模型,因爲不一樣平臺會有不一樣指令集內存操做,因此JMM經過禁止特定類型的編譯器重排序和處理器重排序,爲程序員提供一致的內存可見性保證。即JMM對程序員來講是平臺無關的。
現代的處理器使用寫緩衝區臨時保存要寫入內存的數據。寫緩衝區能夠保證指令流水線持續運行,避免因爲處理器停頓下來等待向內存寫入數據而產生的延遲。同時,經過以批處理的方式刷新寫緩衝區,以及合併寫緩衝區中對同一內存地址的屢次寫,減小對內存總線的佔用。
但每一個處理器上的寫緩衝區,僅僅對它所在的處理器可見。這個特性會對內存操做的執行順序產生重要的影響:處理器對內存的讀/寫操做的執行順序,不必定與內存實際發生的讀/寫操做順序一致。
處理器操做內存的執行結果
處理器和內存的交互
因爲Java使用共享內存進行線程通訊,由於指令重排,A2可能會在A1以前執行,B2在B1以前執行,咱們知道,若是int不指定初始值,則默認值爲0,因此可能出現x=y=0的狀況
假設處理器A和處理器B按程序的順序並行執行內存訪問,
兩個操做訪問同一個變量,且這兩個操做中有一個爲寫操做,此時這兩個操做之間就存在數據依賴性。而重排序是不會改變存在數據依賴關係的兩個操做的執行順序的。
數據依賴性
在JMM中,若是一個操做執行的結果須要對另外一個操做可見,那麼這兩個操做之間必需要存在happens-before關係。這裏提到的兩個操做既能夠是在一個線程以內,也能夠是在不一樣線程之間。
happens-before只是描述內存可見性的,不是指前一個操做必須在後一個操做以前執行。
參考:
《Java編髮編程藝術》