三、volatile

特征

被volatile修飾的變量,具有兩個特征

  1. 保證可見性
  2. 不保證原子性
  3. 禁止指令重排序

關(guān)于內(nèi)存可見性、原子性、有序性,先來了解一下內(nèi)存模型吧~

java內(nèi)存模型(JMM)

  • JMM定義了線程和主內(nèi)存之間的抽相關(guān)
    • 每個線程都會有一個私有的本地內(nèi)存,存儲了共享變量的副本
    • 共享變量存儲再主內(nèi)存中
    • image
特性
  • 原子性
    • 一個操作要么全部執(zhí)行并且執(zhí)行的過程不會被打斷,要么就不執(zhí)行(有點像事務)
    • 下面舉個例子
i = 0;  //是原子操作         
j = i ; //不是! 包含兩個操作 1.讀取i 2.賦值給j
i++;    //不是!三個操作 1.讀取i 2.+1 3.賦值給i 

volatile是無法保證復合操作的原子性。想在多線程環(huán)境下保證原子性,可以通過鎖、synchronized來確保

  • 可見性
    • 多線程訪問一個變量時,一個線程修改變量的值,其他線程能立即看到。
    • 但是,多線程環(huán)境下,一個線程修改變量對其他線程是不可見的!

volatile可以保證可見性。當一個變量被volatile修飾之后,該變量被修改后立即更新到內(nèi)存中,讀取的時候會直接從內(nèi)存中讀取。

  • 有序性
    • 執(zhí)行的順序按照代碼的先后順序執(zhí)行
    • 在java內(nèi)存模型中,為了效率,是允許處理器對指令進行重排序的

volatile禁止指令重排序,來保證一定的有序性

指令重排序:是JVM為了優(yōu)化指令,提高程序運行效率,在不影響單線程程序執(zhí)行結(jié)果的前提下,盡可能地提高并行度。注意是單線程,多線程情況下會有問題啊

原理

在jvm底層 是采用‘內(nèi)存屏障’來實現(xiàn)的

  • 內(nèi)存屏障 (Memory Barrier)

    • 又叫內(nèi)存柵欄,是一個cpu指令
    • 插入一條MB,會告訴編譯器和cpu,什么指令都不能和這條MB指令重排序
    • MB會強制刷出各種CPU cache,如一個Write-Barrier將刷出所有再Barrier之前寫入cache的數(shù)據(jù),因此cpu上的線程都能讀取到這些數(shù)據(jù)的最新版本
  • ****如果一個變量是volatile修飾的,JMM會再寫入這個字段之后插入Write-Barrier指令,在讀這個字段之前插入Read-Barrier指令****,意味著:

    • 一個線程寫入變量A后,任何線程都可以拿到最新值
  • happens-before

    • 兩個操作間具有h-b關(guān)系,并不以為著前一個操作必須要在后一個操作之前執(zhí)行。
    • 僅僅要求前一個操作的執(zhí)行結(jié)果,對后一個操作可見。且前一個操作按順序排在后一個操作之前。

應用場景

  • 狀態(tài)量標記
int a = 0;
//修改后立刻對線程可見 比sync lock有一定的效率提升
volatile bool flag = false;

public void write() {
    a = 2;              //1
    flag = true;        //2
}

public void multiply() {
    if (flag) {         //3
        int ret = a * a;//4
    }
}
  • 單例模式的實現(xiàn) 雙重檢查鎖定(DCL)
懶漢模式
class Singleton{
//為了避免初始化操作的指令重排序 
    private volatile static Singleton instance = null;
 
    private Singleton() {
 
    }
 
    public static Singleton getInstance() {
        if(instance==null) { //B
            synchronized (Singleton.class) {
                if(instance==null)
//在Singleton構(gòu)造函數(shù)體執(zhí)行之前,變量instance可能成為非null!
                    instance = new Singleton(); //A
            }
        }
        return instance;
    }
}
1.線程1進入到//A處,但在構(gòu)造函數(shù)執(zhí)行之前。使實例成為非null
2.線程2進入//B處,實例不為null,將instance引用返回。返回了一個構(gòu)造完整但部分初始化的singleton對象
  • 獨立觀察 獲取最近一次登錄的用戶名
 public volatile String lastUser; //發(fā)布的信息
 
    public boolean authenticate(String user, String password) {
        boolean valid = passwordIsValid(user, password);
        if (valid) {
            User u = new User();
            activeUsers.add(u);
            lastUser = user;
        }
        return valid;
    }

  • 開銷較低的 ‘讀-寫鎖’策略
private volatile int value;
 
    //讀操作,沒有synchronized,提高性能
    public int getValue() { 
        return value; 
    } 
 
    //寫操作,必須synchronized。因為x++不是原子操作
    public synchronized int increment() {
        return value++;
    }

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容