多線程-線程安全-volatile

學習思路:

  1. 線程安全的三大特性
  2. 普通成員變量內存讀取流程
  3. 被volatile修飾後的成員變量

一、線程安全的三大特性

  1. 原子性:指定代碼塊是原子操作(要麼全成功要麼全失敗,不可拆分)
  2. 可見性:修改共享變量時立即同步到主內存,保證對其它線程可見,讀取共享變量時直接從主內存讀取
  3. 有序性:代碼的執行循序和語句的順序保持一致,在JVM中爲了提高運行效率,允許處理器和編譯器對指令進行重排序

二、普通成員變量內存讀取流程

提到成員變量,要分爲兩種情況考慮

  1. 多例(每個線程使用都要重新new一個對象):這種情況下每個線程new對象都會在堆內存開闢一塊新的內存空間,當線程銷燬後會進行垃圾回收,釋放內存,對象對於線程之間來說是相互獨立的,互不影響(成員變量也是相對獨立的),因此也就不存在線程安全問題
  2. 單例(sping bean默認單例):這種情況下多線程公用一個對象實例(在堆內存只有一個對象實例,公用一塊內存空間),使用期間任何一個線程對該對象進行修改,其它線程都可見

備註:單例情況下對象實例在堆中,之所以不被回收是因爲被靜態變量引用,如果引用的不是靜態變量也會被回收

因爲我們現在開發基本都是基於sping容器進行(大都也是單例模式),因此我們下面的講解也是基於單例模式下的成員變量展開

解釋:

衆所周知線程處理都是在CPU上進行,圖中的線程A、線程B可理解爲線程在CPU上執行,每個線程創建的時候都會有自己的私有內存(棧內存),CPU直接操作的並非我們的虛擬機內存,而是計算機高速緩存(如寄存器)然後操作棧內存,這裏的本地內存可理解爲棧內存,主內存可理解爲jMM堆內存

  1. 線程A寫入數據:首先將數據寫入本地內存,然後再寫入主內存
  2. 線程B讀數據先從本地內存讀取,如果沒有從主內存讀取,如果有直接返回
  3. 如果線程A修改數據同步到主內存後,線程B讀取數據發現本地內存有,但主內存的數據還沒有同步到本地內存,這個時候線程B讀到的數據仍爲舊數據,從而可見性沒有得到保障

三、被volatile修飾後的成員變量

volatile關鍵字解決的問題:

  1. 可見性:如下圖線程在讀寫時將本地內存置爲失效,直接讀寫主內存,從而保證了可見性
  2. 有序性:利用各種內存屏障禁止指令重排序(指令重排序時不能把後面的指令重排序到內存屏障之前的位置)
    1. 在每個volatile寫操作的前面插入一個StoreStore屏障;
    2. 在每個volatile寫操作的後面插入一個StoreLoad屏障;
    3. 在每個volatile讀操作的後面插入一個LoadLoad屏障;
    4. 在每個volatile讀操作的後面插入一個LoadStore屏障
  3. 原子性:對volatile修飾變量的單獨讀寫有原子性,對volatile++這種複合操作(實際是先查後修改兩步操作)不具有原子性(原子操作具有原子性,複合操作不具有原子性)(如果要保證複合操作的原子性,可考慮各種鎖機制)

 

公衆號主要記錄各種源碼、面試題、微服務技術棧,幫忙關注一波,非常感謝

 

 

 

 

 

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