關於java併發程式設計的相關文章都是閱讀了《java併發程式設計實戰》之後的讀書筆記總結,另外本文還參考和引用了Java 理論與實踐: 正確使用 Volatile 變數
在java的鎖機制中(例如synchronized),主要包含了兩種特性,即原子性(互斥)和可見性。原子性即一次只允許一個執行緒能夠持有某個特定的鎖,並訪問其程式碼塊。因此原子性可以用於實現對共享資料對協調訪問,一次只有一個執行緒可以訪問其共享物件。
volatile保證可見性
java中的volatile關鍵字可以被認為是一種輕量級的synchronized機制,訪問volatile變數的時候並不會執行加鎖操作,不會導致執行緒阻塞,同時它又和鎖機制一樣,都具備可見性的特性(但是並不具備原子特性)。當把變數生命為volatile型別後,編譯器與執行時都會注意到這個變數是共享的,因此不會把這個變數上的操作與其他記憶體操作一起重排序。volatile變數不會被快取在暫存器或者其他處理器不可見的地方。因此在讀取volatile變數的時候總是會返回最新寫入的值。
volatile提供執行緒安全
volatile變數可以用於提供執行緒安全,但是必須同時滿足以下兩個條件:
對變數的寫操作不依賴於當前的值
該變數沒有包含在具有其他變數的不變式之中
正確使用volatile關鍵字
在使用volatile關鍵字的時候,要始終牢記一個原則--只有在狀態真正獨立於程式內其他內容時才能使用 volatile。
狀態標識:
volatile變數可以作為一個布林狀態標識,用於指示發生一次重要性時間,例如停止執行緒等
class Test {
static volatile boolean isRunning;
private static class TestThread extends Thread {
public void run() {
while (!isRunning) {
System.out.println("the thread is still running");
}
}
}
public static void main(String[] args) {
new TestThread().start();
try {
Thread.sleep(2000);
} catch (InterruptedException e) {
e.printStackTrace();
}
isRunning = true;
}
}複製程式碼
在上面的例子中,我們在isRunning這個布林變數前面加上了volatile進行修飾,以確保在main執行緒當中對它的修改能夠被我們開啟的子執行緒所看到。(如果我們沒有新增volatile關鍵字,有可能子執行緒會持續執行而看不到isRunning的值的改變)
一次性安全釋出:
在缺乏同步的情況下,可能會遇到某個物件引用的更新值(由另一個執行緒寫入)和該物件狀態的舊值同時存在。通過使用volatile關鍵字,我們可以實現安全釋出物件。
public class BackgroundFloobleLoader {
public volatile Flooble theFlooble;
public void initInBackground() {
theFlooble = new Flooble(); // this is the only write to theFlooble
}
}
public class SomeOtherClass {
public void doWork() {
while (true) {
// use the Flooble, but only if it is ready
if (floobleLoader.theFlooble != null)
doSomething(floobleLoader.theFlooble);
}
}
}複製程式碼
⚠️上面的安全釋出的必要條件是:被髮布的物件必須是執行緒安全的,或者是有效的不可變物件(有效不可變意味著物件的狀態在釋出之後永遠不會被修改)。volatile 型別的引用可以確保物件的釋出形式的可見性,但是如果物件的狀態在釋出後將發生更改,那麼就需要額外的同步。
獨立觀察:
定期釋出觀察結果供程式內部使用。如下面的例子所示:
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;
}
}複製程式碼
⚠️上面這種用例要求被髮布的值是有效不可變的 —— 即值的狀態在釋出後不會更改
開銷較低的讀-寫鎖策略:
如果對於某個變數的值的讀操作遠遠超過寫操作,我們可以通過將內建鎖(幫助我們實現操作的原子性)和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++;
}
}複製程式碼