volatile關鍵字解釋和使用
一、java記憶體模型的相關概念:原子性、可見性與有序性
原子性:
原子是世界上的最小單位,具有不可分割性。比如 a=0;(a非long和double型別) 這個操作是不可分割的,那麼我們說這個操作時原子操作。再比如:a++; 這個操作實際是a = a + 1;是可分割的,所以他不是一個原子操作。非原子操作都會存線上程安全問題,需要我們使用同步技術(sychronized)來讓它變成一個原子操作。一個操作是原子操作,那麼我們稱它具有原子性。java的concurrent包下提供了一些原子類。比如:AtomicInteger、AtomicLong、AtomicReference等。
在 Java 中 synchronized 和在 lock、unlock 中操作保證原子性。
可見性:
可見性,是指執行緒之間的可見性,一個執行緒修改的狀態對另一個執行緒是可見的。也就是一個執行緒修改的結果。另一個執行緒馬上就能看到。比如:用volatile修飾的變數,就會具有可見性。volatile修飾的變數不允許執行緒內部快取和重排序,即直接修改記憶體。所以對其他執行緒是可見的。但是這裡需要注意一個問題,volatile只能讓被他修飾內容具有可見性,但不能保證它具有原子性。比如 volatile int a = 0;之後有一個操作 a++;這個變數a具有可見性,但是a++ 依然是一個非原子操作,也就是這個操作同樣存線上程安全問題。
在 Java 中 volatile、synchronized 和 final 實現可見性。
有序性:
Java 語言提供了 volatile 和 synchronized 兩個關鍵字來保證執行緒之間操作的有序性,volatile 是因為其本身包含“禁止指令重排序”的語義,synchronized 是由“一個變數在同一個時刻只允許一條執行緒對其進行 lock 操作”這條規則獲得的,此規則決定了持有同一個物件鎖的兩個同步塊只能序列執行。
二、volatile原理
Java語言提供了一種稍弱的同步機制,即volatile變數,用來確保將變數的更新操作通知到其他執行緒。當把變數宣告為volatile型別後,編譯器與執行時都會注意到這個變數是共享的,因此不會將該變數上的操作與其他記憶體操作一起重排序。volatile變數不會被快取在暫存器或者對其他處理器不可見的地方,因此在讀取volatile型別的變數時總會返回最新寫入的值。
在訪問volatile變數時不會執行加鎖操作,因此也就不會使執行執行緒阻塞,因此volatile變數是一種比sychronized關鍵字更輕量級的同步機制。所以volatile具有的是可見性和有序性
三、volatile的使用條件
1.執行結果並不依賴變數的當前值,或者能夠確保只有單一的執行緒修改變數的值。
2.變數不需要與其他的狀態變數共同參與不變約束。
四、可以使用volatile的例子
1.狀態標記量
volatile boolean shutdownRequested; ... public void shutdown() { shutdownRequested = true; } public void doWork() { while (!shutdownRequested) { // do stuff } }
2.獨立觀察
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; } }
3.開銷較低的讀-寫鎖策略
@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++; } }
五、先行發生原則、指令重排序、記憶體屏障
先行發生原則:在電腦科學中,先行發生原則是兩個事件的結果之間的關係,如果一個事件發生在另一個事件之前,結果必須反映,即使這些事件實際上是亂序執行的(通常是優化程式流程)。
指令重排序:為了在不改變程式執行結果的前提下,優化程式的執行效率。需要注意的是,這裡所說的不改變執行結果,指的是不改變單執行緒下的程式執行結果。
記憶體屏障:記憶體屏障也稱為記憶體柵欄或柵欄指令,是一種屏障指令,它使CPU或編譯器對屏障指令之前和之後發出的記憶體操作執行一個排序約束。 這通常意味著在屏障之前釋出的操作被保證在屏障之後釋出的操作之前執行。共有四種類型:LoadLoad、StoreStore、LoadStore、StoreLoad。
例子來自:https://www.ibm.com/developerworks/cn/java/j-jtp06197.html