volatile原理以及使用場景

 volatile的特性

volatile是一個特殊的修飾符,只有成員變量才能使用它。 當一個共享變量被volatile修飾時,它會保證修改的值會立即被更新到主存,當有其他線程需要讀取時,它會去內存中讀取新值。

volatile特性一:內存可見性,即線程A對volatile變量的修改,其他線程獲取的volatile變量都是最新的。

volatile特性二:可以禁止指令重排序

volatile關鍵字保證了操作的可見性,但是不能保證對變量的操作是原子性。

volatile的原理

加入volatile關鍵字時,生成的彙編代碼會多出一個lock前綴指令

  lock前綴指令實際上相當於一個內存屏障(也成內存柵欄),內存屏障會提供3個功能:

  1)它確保指令重排序時不會把其後面的指令排到內存屏障之前的位置,也不會把前面的指令排到內存屏障的後面;即在執行到內存屏障這句指令時,在它前面的操作已經全部完成;

  2)它會強制將對緩存的修改操作立即寫入主存;

  3)如果是寫操作,它會導致其他CPU中對應的緩存行無效。

volatitle 寫-讀的內存語義

  • volatile寫的內存語義:當寫一個volatitle變量時,JMM會把該線程對應的 本地內存中的共享變量值刷新到主內存中。
  • volatile讀的內存語義:當讀一個volatitle變量時,JMM會把該線程對應的本地內存置爲無效。線程需要從主內存中重新讀取共享變量。

使用volatile關鍵字的場景

1、狀態標記量

volatile boolean flag= false;
 
// 線程1
context = loadContext();
flag= true;
 
// 線程2
while(!flag) {
    sleep();
}
doSomethingWithConfig(context);

2、雙重檢查

所謂雙重檢查加鎖機制,指的是:並不是每次進入getInstance方法都需要同步,而是先不同步,進入方法過後,先檢查實例是否存在,如果不存在才進入下面的同步塊,這是第一重檢查。進入同步塊過後,再次檢查實例是否存在,如果不存在,就在同步的情況下創建一個實例,這是第二重檢查。這樣一來,就只需要同步一次了,從而減少了多次在同步情況下進行判斷所浪費的時間。 

public class Singleton {
    private volatile static Singleton instance = null;
 
    private Singleton() {}
 
    public static Singleton getInstance() {
        if (instance == null) {
            synchronized (Singleton.class) {// 1
                if (instance == null) {// 2
                    instance = new Singleton();// 3
                }
            }
        }
        return instance;
    }
}

3、獨立觀察(independent observation)

安全使用 volatile 的另一種簡單模式是:定期 “發佈” 觀察結果供程序內部使用。例如,假設有一種環境傳感器能夠感覺環境溫度。一個後臺線程可能會每隔幾秒讀取一次該傳感器,並更新包含當前文檔的 volatile 變量。然後,其他線程可以讀取這個變量,從而隨時能夠看到最新的溫度值。

使用該模式的另一種應用程序就是收集程序的統計信息。下面程序 展示了身份驗證機制如何記憶最近一次登錄的用戶的名字。將反覆使用lastUser引用來發布值,以供程序的其他部分使用。

public class UserManager {
    public volatile String lastUser;
 
    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;
    }
}

4、“volatile bean” 模式

volatile bean 模式適用於將 JavaBeans 作爲“榮譽結構”使用的框架。在 volatile bean 模式中,JavaBean 被用作一組具有 getter 和/或 setter 方法 的獨立屬性的容器。volatile bean 模式的基本原理是:很多框架爲易變數據的持有者(例如 HttpSession)提供了容器,但是放入這些容器中的對象必須是線程安全的。

在 volatile bean 模式中,JavaBean 的所有數據成員都是 volatile 類型的,並且 getter 和 setter 方法必須非常普通 —— 除了獲取或設置相應的屬性外,不能包含任何邏輯。此外,對於對象引用的數據成員,引用的對象必須是有效不可變的。(這將禁止具有數組值的屬性,因爲當數組引用被聲明爲 volatile 時,只有引用而不是數組本身具有 volatile 語義)。對於任何 volatile 變量,不變式或約束都不能包含 JavaBean 屬性。下面代碼示例展示了遵守 volatile bean 模式的 JavaBean:

@ThreadSafe
public class Person {
    private volatile String firstName;
    private volatile String lastName;
    private volatile int age;
 
    public String getFirstName() { return firstName; }
    public String getLastName() { return lastName; }
    public int getAge() { return age; }
 
    public void setFirstName(String firstName) { 
        this.firstName = firstName;
    }
 
    public void setLastName(String lastName) { 
        this.lastName = lastName;
    }
 
    public void setAge(int age) { 
        this.age = age;
    }
}

5 、開銷較低的讀-寫鎖策略

目前爲止,您應該瞭解了 volatile 的功能還不足以實現計數器。因爲++x 實際上是三種操作(讀、添加、存儲)的簡單組合,如果多個線程湊巧試圖同時對 volatile 計數器執行增量操作,那麼它的更新值有可能會丟失。

然而,如果讀操作遠遠超過寫操作,您可以結合使用內部鎖和 volatile 變量來減少公共代碼路徑的開銷。清單 6 中顯示的線程安全的計數器使用synchronized確保增量操作是原子的,並使用volatile 保證當前結果的可見性。如果更新不頻繁的話,該方法可實現更好的性能,因爲讀路徑的開銷僅僅涉及 volatile 讀操作,這通常要優於一個無競爭的鎖獲取的開銷。

@ThreadSafe
public class CheesyCounter {
    // Employs the cheap read-write lock trick
    // All mutative operations MUST be done with the 'this' lock held
    @GuardedBy("this") private volatile int value;
 
    public int getValue() { return value; }
 
    public synchronized int increment() {
        return value++;
    }
}

 

參考文獻

java併發編程的藝術-方騰飛 

 

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