併發編程--線程安全性

安全性

Volatile

保證共享變量可見性,實現跨線程寫入的可見性 。
在修改帶有 volatile 修飾的成員變量時,會多一個lock 指令。lock是一種控制指令,在多處理器環境下,lock彙編指令能夠基於總線鎖或者緩存鎖的機制來達到可見性的一個效果。緩存

CPU高速緩存

L1,L2,L3 3級高速緩存安全

緩存一致性

高速緩存的存在之後,每一個 CPU 的處理過程是,先將計算須要用到的數據緩存在 CPU 高速緩存中,在 CPU進行計算時,直接從高速緩存中讀取數據而且在計算完成以後寫入到緩存中。在整個運算過程完成後,再把緩存中的數據同步到主內存。因爲在多CPU中,每一個線程可能會運行在不一樣的CPU內,而且每一個線程擁有本身的高速緩存。同一份數據可能會被緩存到多個CPU中,若是在不一樣CPU中運行的不一樣線程看到同一分內存的緩存值不同就會存在緩存不一致的問題 爲了解決緩存不一致的問題,主要提供了兩種解決辦法多線程

  1. 總線鎖
  2. 緩存鎖 總線鎖和緩存鎖
    總線鎖,簡單來講就是,在多cpu下,當其中一個處理器要對共享內存進行操做的時候,在總線上發出一個LOCK#信號,這個信號使得其餘處理器沒法經過總線來訪問到共享內存中的數據,總線鎖定把CPU和內存之間的通訊鎖住了,這使得鎖按期間,其餘處理器不能操做其餘內存地址的數據,因此總線鎖定的開銷比較大,這種機制顯然是不合適的。
    如何優化呢?
    最好的方法就是控制鎖的保護粒度,咱們只須要保證對於被多個CPU緩存的同一份數據是一致的就行。因此引入了緩存鎖,它核心機制是基於緩存一致性協 議來實現的。

緩存一致性協議

常見的協議有MSI,MESI,MOSI 等。最多見的就是 MESI 協議。app

MESI

MESI 表示緩存行的四種狀態,分別是異步

  1. M(Modify) 表示共享數據只緩存在當前CPU緩存中,而且是被修改狀態,也就是緩存的數據和主內存中的數據不一致
  2. E(Exclusive) 表示緩存的獨佔狀態,數據只緩存在當前CPU 緩存中,而且沒有被修改
  3. S(Shared) 表示數據可能被多個CPU緩存,而且各個緩存中的數據和主內存數據一致
  4. I(Invalid) 表示緩存已經失效storebuffer 異步執行致使CPU的亂序執行->重排序
    在 MESI 協議中,每一個緩存的緩存控制器不只知道本身的讀寫操做,並且也監聽(snoop)其它 Cache 的讀寫操做
    對於 MESI 協議,從 CPU 讀寫角度來講會遵循如下原則:
  • CPU 讀請求:緩存處於 M、E、S 狀態均可以被讀取,I 狀 態 CPU 只能從主存中讀取數據
  • CPU 寫請求:緩存處於 M、E 狀態才能夠被寫。對於 S 狀態的寫,須要將其餘 CPU 中緩存行置爲無效纔可寫使用總線鎖和緩存鎖機制以後,CPU 對於內存的操做大概能夠抽象成下面這樣的結構。從而達到緩存一致性效果

MESI優化帶來的可見性問題

MESI 協議雖然能夠實現緩存的一致性,可是也會存在一些問題。就是各個 CPU 緩存行的狀態是經過消息傳遞來進行的。如 果 CPU0 要對一個在緩存中共享的變量進行寫入,首先須要發送一個失效的消息給到其餘緩存了該數據的 CPU。而且要等到他們的確認回執。CPU0 在這段時間內都會處於阻塞狀態。爲了不阻塞帶來的資源浪費。

在 cpu 中引入了 Store Bufferes。
CPU0 只須要在寫入共享數據時,直接把數據寫入到storebufferes中,同時發送invalidate消息,而後繼續去處理其餘指令。當收到其餘全部CPU發送了 invalidate acknowledge消息時,再將 store bufferes 中的數據數據存儲至cacheline中。最後再從緩存行同步到主內存。

存在的問題:
exeToCPU0和exeToCPU1分別在兩個獨立的CPU上執行。假如 CPU0 的緩存行中緩存了 isFinish 這個共享變量,而且狀態爲(E)、而 Value 多是(S)狀態。 那麼這個時候,CPU0 在執行的時候,會先把 value=10 的指令寫入到storebuffer中。而且通知給其餘緩存了該value變量的 CPU。在等待其餘 CPU 通知結果的時候,CPU0 會繼續執行 isFinish=true 這個指令。而由於當前 CPU0 緩存了 isFinish 而且是 Exclusive 狀態,因此能夠直接修改 isFinish=true。這個時候 CPU1 發起 read操做去讀取 isFinish 的值可能爲 true,可是 value 的值不等於 10。
storebuffer實際上是一個異步操做,出現了緩存指令的重排序,CPU的亂序執行。oop

內存屏障

Store Memory Barrier(寫屏障) 告訴處理器在寫屏障以前的全部已經存儲在存儲緩存(store bufferes)中的數據同步到主內存,簡單來講就是使得寫屏障以前的指令的結果對屏障以後的讀或者寫是可見的 Load Memory Barrier(讀屏障) 處理器在讀屏障以後的讀操做,都在讀屏障以後執行。配合寫屏障,使得寫屏障以前的內存更新對於讀屏障以後的讀操做是可見的 Full Memory Barrier(全屏障) 確保屏障前的內存讀寫操做的結果提交到內存以後,再執行屏障後的讀寫操做。
內存屏障的做用能夠經過防止 CPU 對內存的亂序訪問來保證共享數據在多線程並行執行下的可見性。優化

volatile->lock(緩存鎖) -> 內存屏障->可見性

JMM的內存模型

JMM 全稱是 Java Memory Model。JMM實際上就是提供了合理的禁用緩存以及禁止重排序的方法。因此它最核心的價值在於解決可見性和有序性。
語言級別抽象內存模型->volatile、synchronized、final、(happens-before) 重排序 ->數據依賴規則
JMM內存屏障
Happens-Before 規則 可見性的保障線程

  1. 程序的順序規則
  2. volatile規則
  3. 傳遞性
  4. start規則
  5. join規則
  6. synchronized監視器規則
相關文章
相關標籤/搜索