Volatile關鍵字

內存模型

CPU指令執行速度快,數據保存在主存中,即物理內存,爲避免降低CPU執行速度,將數據從主存中copy到高速緩存中去,直接從高速緩存讀取和寫入數據,執行完畢後再將數據刷回到主存。

當多核,多線程的時候,對內存中同一個變量的調用,放入各自的高速緩存去執行,可能就會造成緩存不一致的問題。通常稱這種被多個線程訪問的變量爲共享變量

一個變量在多個CPU中都存在緩存(一般在多線程編程時纔會出現),那麼就可能存在緩存不一致的問題。

爲了解決緩存不一致性問題,通常來說有以下2種解決方法:

  • 通過在總線加LOCK#鎖的方式
    對總線加鎖,阻塞了其他CPU對其他部件的訪問(如主存),從而使得只能有一個CPU能使用這個變量的內存。但會導致其他CPU無法訪問內存,導致效率低下。
  • 通過緩存一致性協議
    它核心的思想是:當CPU寫數據時,如果發現操作的變量是共享變量,即在其他CPU中也存在該變量的副本,會發出信號通知其他CPU將該變量的緩存行置爲 無效狀態,因此當其他CPU需要讀取這個變量時,發現自己緩存中緩存該變量的緩存行是無效的,那麼它就會從內存重新讀取。

併發性概念

原子性

一個操作或者多個操作 要麼全部執行並且執行的過程不會被任何因素打斷,要麼就都不執行。

可見性

當多個線程訪問同一個變量時,一個線程修改了這個變量的值,其他線程能夠立即看得到修改的值。

這裏寫圖片描述

有序性

程序執行的順序按照代碼的先後順序執行。

指令重排序:處理器爲了提高程序運行效率,可能會對輸入代碼進行優化,它不保證程序中各個語句的執行先後順序同代碼中的順序一致,但是它會保證程序最終執行結果和代碼順序執行的結果是一致的。

★注:
1、處理器在進行重排序時是會考慮指令之間的數據依賴性,如果一個指令Instruction 2必須用到Instruction 1的結果,那麼處理器會保證Instruction 1會在Instruction 2之前執行。
2、指令重排序不會影響單個線程的執行,但是會影響到多個線程併發執行的正確性。

要想併發程序正確地執行,必須要保證原子性、可見性以及有序性。

內存模型

Java內存模型規定所有的變量都是存在主存當中(類似於前面說的物理內存),每個線程都有自己的工作內存(類似於前面的高速緩存)。線程對變量的所有操作都必須在工作內存中進行,而不能直接對主存進行操作。並且每個線程不能訪問其他線程的工作內存。

java中對於原子性、可見性、有序性的保證:

java保證原子性

Java內存模型只保證了基本讀取和賦值(而且必須是將數字賦值給某個變量,變量之間的相互賦值不是原子操作)是原子性操作。

x = 10;         //語句1
y = x;         //語句2

語句1是直接將數值10賦值給x,也就是說線程執行這個語句的會直接將數值10寫入到工作內存中。
語句2實際上包含2個操作,它先要去讀取x的值,再將x的值寫入工作內存,雖然讀取x的值以及 將x的值寫入工作內存 這2個操作都是原子性操作,但是合起來就不是原子性操作了

如果要實現更大範圍操作的原子性,可以通過synchronized和Lock來實現

java保證可見性

對於可見性,Java提供了volatile關鍵字來保證可見性。

當一個共享變量被volatile修飾時,它會保證修改的值會立即被更新到主存,當有其他線程需要讀取時,它會去內存中讀取新值

通過synchronized和Lock也能夠保證可見性,synchronized和Lock能保證同一時刻只有一個線程獲取鎖然後執行同步代碼,並且在釋放鎖之前會將對變量的修改刷新到主存當中。因此可以保證可見性。

java保證有序性

在Java內存模型中,允許編譯器和處理器對指令進行重排序,但是重排序過程不會影響到單線程程序的執行,卻會影響到多線程併發執行的正確性。

volatile關鍵字來保證一定的“有序性,。另外可以通過synchronized和Lock來保證有序性,

java內存模型:happens-before原則,具備一定先天的有序性,無需任何其他手段就能保證有序性。

這裏寫圖片描述

程序次序規則:在單線程中,對於不存在數據依賴性的指令會出現指令重排序,但是最終結果和代碼執行順序結果是正確的,因此在單線程中看起來是程序有序執行的,但是不適用多線程併發操作。

深入解析Volatile關鍵字

1、volatile保證了可見性

不同線程對這個變量進行操作時的可見性,即一個線程修改了某個變量的值,這新值對其他線程來說是立即可見的。禁止進行指令重排序

2、volatile不保證原子性

volatile沒辦法保證對變量的操作的原子性。

public class Test {
    public volatile int inc = 0;

    public void increase() {
        inc++;
    }

    public static void main(String[] args) {
        final Test test = new Test();
        for(int i=0;i<10;i++){
            new Thread(){
                public void run() {
                    for(int j=0;j<1000;j++)
                        test.increase();
                };
            }.start();
        }

        while(Thread.activeCount()>1)  //保證前面的線程都執行完
            Thread.yield();
        System.out.println(test.inc);
    }
}

3、volatile一定程度保證有序性

volatile關鍵字禁止指令重排序有兩層意思:
1)當程序執行到volatile變量的讀操作或者寫操作時,在其前面的操作的更改肯定全部已經進行,且結果已經對後面的操作可見;在其後面的操作肯定還沒有進行;
2)在進行指令優化時,不能將在對volatile變量訪問的語句放在其後面執行,也不能把volatile變量後面的語句放到其前面執行。

volatile關鍵字使用場景

volatile關鍵字是無法替代synchronized關鍵字的,因爲volatile關鍵字無法保證操作的原子性。通常來說,使用volatile必須具備以下2個條件:
1)對變量的寫操作不依賴於當前值
2)該變量沒有包含在具有其他變量的不變式中

Java中使用volatile的幾個場景:

1.狀態標記量

volatile boolean inited = false;
//線程1:
context = loadContext();  
inited = true;            

//線程2:
while(!inited ){
sleep()
}
doSomethingwithconfig(context);

2.double check

/**
單例
*/
class Singleton{
    private volatile static Singleton instance = null;

    private Singleton() {}

    public static Singleton getInstance() {
        if(instance==null) {
            synchronized (Singleton.class) {
                if(instance==null)
                    instance = new Singleton();
            }
        }
        return instance;
    }
}
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章