Java多執行緒程式設計那些事:volatile解惑

weixin_34253539發表於2017-10-19

1、 前言

\\

volatile關鍵字可能是Java開發人員“熟悉而又陌生”的一個關鍵字。本文將從volatile關鍵字的作用、開銷和典型應用場景以及Java虛擬機器對volatile關鍵字的實現這幾個方面為讀者全面深入剖析volatile關鍵字。

\\

volatile字面上有“揮發性的,不穩定的”意思,它是用於修飾可變共享變數(Mutable Shared Variable)的一個關鍵字。所謂“共享”是指一個變數能夠被多個執行緒訪問(包括讀/寫),所謂“可變”是指變數的值可以發生變化。換而言之,volatile關鍵字用於修飾多個執行緒併發訪問的同一個變數,這些執行緒中至少有一個執行緒會更新這個變數的值。我們稱volatile修飾的變數為volatile變數。我們知道鎖的作用包括保障原子性、保障可見性以及保障有序性。volatile常被稱為“輕量級鎖”,其作用與鎖有類似的地方——volatile也能夠保障原子性(僅保障long/double型變數訪問操作的原子性)、保障可見性以及保障有序性。

\\

本文所提及的“Java虛擬機器”如無特別說明,均特指Oracle公司的HotSpot Java虛擬機器。

\\

2. 保障long/double型變數訪問操作的原子性

\\

不可分割的操作被稱為原子操作(Atomic Operation)。所謂不可分割(Indivisible)是指一個操作從其執行執行緒以外的其他執行緒看來,該操作要麼已經完成要麼尚未開始,也就是說其他執行緒不會看到該操作的中間結果。如果一個操作是原子操作,那麼我們就稱該操作具有原子性(Atomicity)。

\\

Java語言規範(Java Language Specification,JLS)規定,Java語言中針對long/double型以外的任何變數(包括基礎型別變數和引用型變數)進行的讀、寫操作都是原子操作,即Java語言規範本身並不規定針對long/double型變數進行讀、寫操作具有原子性。一個long/double型變數的讀/寫操作在32位Java虛擬機器下可能會被分解為兩個子步驟(比如先寫低32位,再寫高32位)來實現,這就導致一個執行緒對long/double型變數進行的寫操作的中間結果可以被其他執行緒所觀察到,即此時針對long/double型變數的訪問操作不是原子操作。清單1所示的實驗展示了這點。

\\

清單1 long/double型變數寫操作的原子性問題Demo

\\
\/**\\* 本Demo必須使用32位Java虛擬機器才能看到非原子操作的效果. \u0026lt;br\u0026gt;\\* 執行本Demo時也可以指定虛擬機器引數“-client”\\*\\* @author Viscent Huang\\*/\\public class NonAtomicAssignmentDemo implements Runnable {\\static long value = 0;\\private final long valueToSet;\\public NonAtomicAssignmentDemo(long valueToSet) {\\this.valueToSet = valueToSet;\\}\\public static void main(String[] args) {\\// 執行緒updateThread1將data更新為0\\Thread updateThread1 = new Thread(new NonAtomicAssignmentDemo(0L));\\// 執行緒updateThread2將data更新為-1\\Thread updateThread2 = new Thread(new NonAtomicAssignmentDemo(-1L));\\updateThread1.start();\\updateThread2.start();\\// 不進行實際輸出的OutputStream\\final DummyOutputStream dos = new DummyOutputStream();\\try (PrintStream dummyPrintSteam = new PrintStream(dos);) {\\// 共享變數value的快照(即瞬間值)\\long snapshot;\\while (0 == (snapshot = value) || -1 == snapshot) {\\// 不進行實際的輸出,僅僅是為了阻止JIT編譯器做迴圈不變表示式外提優化\\dummyPrintSteam.print(snapshot);\\}\\System.err.printf(\"Unexpected data: %d(0x%016x)\

相關文章