volatile小總結

volatile

舉個例子

一個線程讀取數據,一個線程修改數據,存在數據的不一致性

機器硬件CPU與JMM

CPU Cache模型

我們都知道,在CPU和內存之間,有着天壤之別的處理速度.高速的cpu和低速的主存,我們不可能讓高速的CPU一直等着低速的內存完成IO才繼續操作,所以在CPU和主存之間引入了多級緩存來緩解這一問題.目的是提高CPU吞吐量.

在這裏插入圖片描述

當需要數據的時候CPU會首先在Cache L1中取去找,找不到會在L2中找,都找不到纔會在主存中找

在這裏插入圖片描述

CPU緩存的一致性問題

每一個CPU修改內存數據的步驟:

1.從內存中把數據讀到cache中

2.在cache中更新數據

3.把更新的結果刷新到內存

當cpu1把i=3這條數據讀到cache中進行修改時,這時候還沒來得及寫回主存,cpu2又來拿i的值,那麼他拿到的i值有可能是3也有可能是4.

在這裏插入圖片描述

解決方案:

1) 總線加鎖 (粒度太大)
2) MESI

​ a. 讀操作: 不做任何事情,把cache中的數據讀到寄存器

​ b. 寫操作: 發出信號通知其他的cpu將變量的cache line置爲無效狀態,其他的cpu要訪問這個變量的時候,只能從內存中獲取.

​ Cache line: CPU的cache中會增加很多的Cache line,CPU要讀一個變量的時候會先檢測這個變量的Cache line是不是有效的,有效的時候再去讀,無效的時候就要去內存中讀.

Java內存模型

在這裏插入圖片描述

  1. 主存中的數據所有線程都可以訪問

  2. 每個線程都有自己的工作空間,(本地內存)

  3. 工作空間數據:局部變量,內存的副本

  4. 線程不能直接修改內存中的數據,只能讀到工作空間來修改,修改完成後刷新到內存

volatile語義分析

volatile作用:讓其他線程能夠馬上感知到某一線程對某個變量的修改

保證可見性

對共享變量的修改,其他線程馬上能感知到

當你對一個共享變量加了一個volatile時,某線程要對這個變量進行修改,會向其他線程發出控制信號,控制信號映射到硬件上就是將cache line置爲無效.

在這裏插入圖片描述

那麼這時候其他線程從主存中讀取到的數據一定就是最新的嗎?

不一定,因爲volatile不能保證原子性,比如多個線程對某共享變量進行++操作,但由於++操作本身就不是一個原子操作,所以其他線程讀到內存中的數據可能是還沒被寫回來的數據,所以造成了數據的不一致.

保證有序性

重排序:輸入程序的代碼順序並不是實際執行的順序.jvm會在在編譯階段和指令優化階段進行代碼順序的調整,爲了提高效率.

重排序對單線程沒有影響,對多線程有影響.

對於volatile修飾的變量:

​ 1) volatile之前的代碼不能調整到他的後面

​ 2) volatile之後的代碼不能調整到他的前面

volatile的原理和實現機制

volatile可見性的實現就是藉助了CPU的lock指令,通過在寫volatile的機器指令前加上lock前綴,使寫volatile具有以下兩個原則:

  1. 寫volatile時處理器會將緩存寫回到主內存。

  2. 一個處理器的緩存寫回到內存會導致其他處理器的緩存失效。

volatile有序性的保證就是通過禁止指令重排序來實現的。指令重排序包括編譯器和處理器重排序,JMM會分別限制這兩種指令重排序。

  1. 在每個volatile寫操作的前面插入一個StoreStore屏障,防止寫volatile與後面的寫操作重排序。
  2. 在每個volatile寫操作的後面插入一個StoreLoad屏障,防止寫volatile與後面的讀操作重排序。
  3. 在每個volatile讀操作的後面插入一個LoadLoad屏障,防止讀volatile與後面的讀操作重排序。
  4. 在每個volatile讀操作的後面插入一個LoadStore屏障,防止讀volatile與後面的寫操作重排序。

volatile使用場景

狀態標誌

package sync;

public class ShutDownDemo extends Thread{
    private volatile boolean started = true;

    @Override
    public void run() {
        while (started){
            dowork();

        }
    }

    public void shutDown() {
        started = false;
    }
}

雙重檢查鎖定

單例模式的應用

package sync;

public class Singleton {
    private volatile static Singleton singleton;
    private Singleton(){}
    
    public static Singleton getInstance(){
        if (singleton == null){
            synchronized (Singleton.class){
                if (singleton == null){
                    singleton = new Singleton();
                }
            }
        }
        return singleton;
    }
}

需要利用順序性

volatile與synchronized的區別

使用上的區別

volatile只能修飾變量,synchronized可以修飾方法和語句塊

對原子性的保證

synchronized可以保證原子性,volatile不能保證原子性

對可見性的保證

都可以保證可見性,但實現原理不同

volatile對變量加了lock

synchronized使用monitorenter和monitorexit

對有序性的保證

volatile能夠保證有序性,synchronized也可以保證有序性,但是代價(重量級)太大,由併發退化到串行

其他

synchronized會引起阻塞

volatile不會引起阻塞

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