標籤:nbsp 位元組 順序 原子類 相關 before strong 實踐 上下文
引言:
在多線程並發編程中synchronized和Volatile都扮演著重要的角色,Volatile是輕量級的synchronized,它在多處理器開發中保證了共用變數的“可見度”。
可見度的意思是當一個線程修改一個共用變數時,另外一個線程能讀到這個修改的值。
Volatile的官方定義
Java語言規範第三版中對volatile的定義如下: java程式設計語言允許線程訪問共用變數,為了確保共用變數能被準確和一致的更新,線程應該確保通過獨佔鎖定單獨獲得這個變數。Java語言提供了volatile,在某些情況下比鎖更加方便。如果一個欄位被聲明成volatile,java線程記憶體模型確保所有線程看到這個變數的值是一致的。
為什麼要使用Volatile?
Volatile變數修飾符如果使用恰當的話,它比synchronized的使用和執行成本會更低,因為它不會引起線程內容相關的切換和調度。
Volatile的使用最佳化
著名的Java並發編程大師Doug lea在JDK7的並發包裡新增一個隊列集合類LinkedTransferQueue,他在使用Volatile變數時,用一種追加位元組的方式來最佳化隊列出隊和入隊的效能。
追加位元組能最佳化效能?這種方式看起來很神奇,但如果深入理解處理器架構就能理解其中的奧秘。讓我們先來看看LinkedTransferQueue這個類,它使用一個內部類類型來定義隊列的頭隊列(Head)和尾節點(tail),而這個內部類PaddedAtomicReference相對於父類AtomicReference只做了一件事情,就將共用變數追加到64位元組。我們可以來計算下,一個對象的引用佔4個位元組,它追加了15個變數共佔60個位元組,再加上父類的Value變數,一共64個位元組。
相關問題:
1)Java 中能建立 volatile 數組嗎?
能,Java 中可以建立 volatile 類型數組,不過只是一個指向數組的引用,而不是整個數組。我的意思是,如果改變引用指向的數組,將會受到 volatile 的保護,但是如果多個線程同時改變數組的元素,volatile 標示符就不能起到之前的保護作用了。
2)volatile 能使得一個非原子操作變成原子操作嗎?
一個典型的例子是在類中有一個 long 類型的成員變數。如果你知道該成員變數會被多個線程訪問,如計數器、價格等,你最好是將其設定為 volatile。為什嗎?因為 Java 中讀取 long 類型變數不是原子的,需要分成兩步,如果一個線程正在修改該 long 變數的值,另一個線程可能只能看到該值的一半(前 32 位)。但是對一個 volatile 型的 long 或 double 變數的讀寫是原子。
3)volatile 修飾符的有過什麼實踐?
一種實踐是用 volatile 修飾 long 和 double 變數,使其能按原子類型來讀寫。double 和 long 都是64位寬,因此對這兩種類型的讀是分為兩部分的,第一次讀取第一個 32 位,然後再讀剩下的 32 位,這個過程不是原子的,但 Java 中 volatile 型的 long 或 double 變數的讀寫是原子的。volatile 修複符的另一個作用是提供記憶體屏障(memory barrier),例如在分布式架構中的應用。簡單的說,就是當你寫一個 volatile 變數之前,Java 記憶體模型會插入一個寫屏障(write barrier),讀一個 volatile 變數之前,會插入一個讀屏障(read barrier)。意思就是說,在你寫一個 volatile 域時,能保證任何線程都能看到你寫的值,同時,在寫之前,也能保證任何數值的更新對所有線程是可見的,因為記憶體屏障會將其他所有寫的值更新到緩衝。
4)volatile 類型變數提供什麼保證?
volatile 變數提供順序和可見度保證,例如,JVM 或者 JIT為了獲得更好的效能會對語句重排序,但是 volatile 類型變數即使在沒有同步塊的情況下賦值也不會與其他語句重排序。 volatile 提供 happens-before 的保證,確保一個線程的修改能對其他線程是可見的。某些情況下,volatile 還能提供原子性,如讀 64 位元資料類型,像 long 和 double 都不是原子的,但 volatile 類型的 double 和 long 就是原子的。
Java高並發同步Volatile的使用