java併發理論摘要

  • 數據依賴
    所謂的數據依賴是指,如果多個java指令之間順序變化會影響結果,那麼說明這個這兩個指令存在數據依賴關係。

  • as-if-serial
    在一個線程中,會考慮數據依賴問題,也就是說如果兩個指令有數據依賴的關係,那麼不會重排序,因爲會影響執行結果。但是在多線程中,是不考慮兩個指令的數據依賴關係的,否則會嚴重影響多處理器的並行效率。as-if-serial是針對單線程來說的。

  • 多線程重排序會影響語義
    多線程(使用共享變量)條件下,重排序就會影響語義。比如控制依賴(if),滿足if條件纔會執行裏面的操作,裏面的操作是使用共享變量做運算,爲了提高指令的併發,會先做運算把結果保存在緩存在,等if真的滿足才賦值,但是這個共享變量因爲是共享的,必然這個值是不固定的,所以執行結果會不相同,破壞了語義。

  • 順序一致性內存模型
    順序一致性內存模型其實是另外一種內存模型,這種內存模型和JVM的內存模型是截然不同的,順序一致性這種內存模型在多線程的情況下,是天然沒有線程之間可見性問題的,順序一致性內存模型中,所有線程都是直接面向主內存的,線程沒有自己的緩存內存。
    1.順序一致性內存模型可以保證單線程內會按照程序順序執行(不重排)
    2.順序一致性內存模型中,線程的操作都是面對主內存的,保證所有線程看到執行順序是一致(內存可見性)
    3.順序一致性內存模型中,能保證所有的讀寫操作都具有原子性(讀寫原子性)

  • volatile內存語義
    volatile的語義就是來解決JVM內存模型中的三個問題的
    1.JVM內存模型中已經通過as-if-serial解決了重排序在單線程內執行結果不變的問題,但是volatile變量讀寫會限制程序上下文的重排序,通過內存屏障
    2.JVM內存模型中用 volatile修飾的變量會特殊處理,每當碰到讀寫 volatile修飾的變量都會喚醒線程內存和主內存的數據交互,保證了volatile修飾的變量具備內存可見性;同時volatile通過限制程序上下文的重排序,通過內存屏障達到程序上下文相關變量的內存可見性
    3.JVM內存模型中用 volatile修飾的變量會特殊處理,會保證變量讀寫事務的原子性

  • 爲什麼需要volatile
    其實我覺得volatile對於提高程序性能是有很大幫助的,volatile其實就像一個開關,你需要滿足你的程序具有非重排序內存可見性和讀寫事務的原子性,那麼你就加上這個關鍵字修飾,不需要就不要加。順序一致內存模型就是默認所有操作都滿足非重排序可見性和原子性,但是這樣會很影響指令的併發性,而JVM內存模型就不一樣,需要和主內存中交互的就加上volatile,否則一些數據的狀態在線程的緩存中就可以了,這些數據對主內存或者其他線程的意義也不大

  • 鎖的內存語義
    常見的鎖主要是兩種,分別是使用synchronized關鍵字的同步方法同步塊和使用Lock接口的實現鎖,這兩種鎖存在相同的語義
    臨界區代碼互斥:同步方法內的代碼或者lock和unLock方法直接的代碼是同步互斥的
    臨界區代碼保證可見性:同步方法內的代碼或者lock和unLock方法的代碼裏面,在獲取鎖的時候會強制到主內存中讀取數據,在釋放鎖時強制將數據從線程緩存寫到主內存中

  • synchronized和Lock和CAS鎖內存語義實現方式
    Lock鎖:
    1.使用AQS實現了線程的掛起,滿足了鎖的代碼互斥
    2.使用了volatile變量status,獲取鎖讀主內存釋放鎖寫主內存,防止了重排序,並且使用上內存屏障和volatile保證可見性。
    3.使用本地CAS方法,CAS內部保證了原子操作,volatile變量status(雖然只是32位,不存在原子問題)也保證了原子讀寫事務
    4.本地CAS方法其實是一個封裝好的一個具有鎖語義的指令,CAS內部實現了鎖,也滿足代碼互斥,非重排,可見性,原子性
    5.就原子性來說,Java實現的原子操作是建立在c++指令的原子(Lock),CAS實現的原子是c++依賴的底層的原子(Lock Cmpxchg), 所以java直接使用CAS就能保證原子性了,原子性的目的主要是兩個指令必須同時生效,否則拿到的數據就是錯誤的數據。
    CAS鎖:
    1.內部使用Lock Cmpxchg指令滿足代碼的互斥
    2.Lock Cmpxchg禁止重排序
    3.Lock Cmpxchg指令確保執行完將數據刷到主內存,滿足可見性
    4…Lock Cmpxchg指令使用緩存鎖定來保證指令執行的原子性
    synchronized鎖:
    內部使用monitorenter和monitorexit字節碼來實現的,可以保證原子性和內存的可見性也會考慮禁止重排

  • 總結
    其實總而言之,JVM內存模型雖然對比順序一致模型併發效率提高了,但是帶來了可見性,原子性,重排序的問題。那麼提高併發的同時有時候也是需要解決可見性,原子性,重排序。
    1.可見性問題:使用volatile關鍵字修飾,被迫讀取寫入主內存來解決
    2.原子性:使用volatile關鍵字修飾的64位變量會滿足,使用CAS滿足多個指令原子性
    3.重排序:使用volatile關鍵字修飾的變量的讀寫規則,在大多數情況下會使用內存屏障來禁止重排序

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章