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併發編程的藝術-方騰飛