volatile關鍵字的做用:保證變量的可見性java
必定要謹記
volatile
關鍵字在 Java 代碼中僅僅保證這個變量是可見的:它不保證原子性。在那些非原子且可由多個線程訪問的易變操做中,必定不可以依賴於 volatile 的同步機制。相反,要使用java.util.concurrent
包的同步語句、鎖類和原子類。它們在設計上可以保證程序是線程安全的。c++
volatile做用的具體描述:git
Java如何保證可見性
Java提供了
volatile
關鍵字來保證可見性。當使用volatile修飾某個變量時,它會保證對該變量的修改會當即被更新到內存中,而且將其它緩存中對該變量的緩存設置成無效,所以其它線程須要讀取該值時必須從主內存中讀取,從而獲得最新的值。github引自- wangzzu的博客文章緩存
上面這段話能十分清楚的說明這個變量的用處了吧。下面配合代碼更加準確的驗證這個問題。安全
代碼引自-- 阿里工程師oldratlee的githubbash
Demo類com.oldratlee.fucking.concurrency.NoPublishDemo
。app
主線程中設置屬性stop
爲true
,以控制在main
啓動的任務線程退出。dom
在主線程屬性stop
爲true
後,但任務線程持續運行,即任務線程中一直沒有讀到新值。ide
出現上述問題說明的代碼:
public class NoPublishDemo { boolean stop = false; public static void main(String[] args) { // LoadMaker.makeLoad(); NoPublishDemo demo = new NoPublishDemo(); Thread thread = new Thread(demo.getConcurrencyCheckTask()); thread.start(); Utils.sleep(1000); System.out.println("Set stop to true in main!"); demo.stop = true; System.out.println("Exit main."); } ConcurrencyCheckTask getConcurrencyCheckTask() { return new ConcurrencyCheckTask(); } private class ConcurrencyCheckTask implements Runnable { @Override public void run() { System.out.println("ConcurrencyCheckTask started!"); // 若是主線中stop的值可見,則循環會退出。 // 在個人開發機上,幾乎必現循環不退出!(簡單安全的解法:在running屬性上加上volatile) while (!stop) { } System.out.println("ConcurrencyCheckTask stopped!"); } } }
例子中主線程的stop變量沒有作任何的顯示定義volatile。
當咱們執行後的打印結果爲:
ConcurrencyCheckTask started! Set stop to true in main! Exit main.
並無打印
ConcurrencyCheckTask stopped!
而且程序尚未結束。
接下來咱們對stop變量添加volatile
volatile boolean stop = false;
程序完美結束。也印證了上文描述中的內容。
組合狀態讀到無效組合
--此時使用volatile則沒法解決同時修改兩個值,數據仍是返回正確的問題。
意思是同時對兩個值修改,可是實際返回確實錯誤的。
程序設計時,會須要多個狀態記錄(狀態能夠是個POJO
對象或是int
等等)。常看到多狀態讀寫沒有同步的代碼,而且寫的同窗會很天然地就忽略了線程安全的問題。
無效組合 是指 歷來沒有設置過的組合。
主線程修改多個狀態,爲了方便檢查,每次寫入有個固定的關係:第2個狀態是第1個狀態值的2倍。在任務線程中讀取多個狀態。
Demo類com.oldratlee.fucking.concurrency.InvalidCombinationStateDemo
。
任務線程中讀到了 第2個狀態不是第1個狀態值2倍的值,便是無效值。
mvn compile exec:java -Dexec.mainClass=com.oldratlee.fucking.concurrency.InvalidCombinationStateDemo
實際測試代碼
public class InvalidCombinationStateDemo { public static void main(String[] args) { CombinationStatTask task = new CombinationStatTask(); Thread thread = new Thread(task); thread.start(); Random random = new Random(); while (true) { int rand = random.nextInt(1000); task.state1 = rand; task.state2 = rand * 2; } } private static class CombinationStatTask implements Runnable { // 對於組合狀態,加 volatile 不能解決問題 volatile int state1; volatile int state2; @Override public void run() { int c = 0; for (long i = 0; ; i++) { int i1 = state1; int i2 = state2; if (i1 * 2 != i2) { c++; System.err.printf("Fuck! Got invalid CombinationStat!! check time=%s, happen time=%s(%s%%), count value=%s|%s\n", i + 1, c, (float) c / (i + 1) * 100, i1, i2); } else { // 若是去掉這個輸出,則在個人開發機上,發生無效組合的機率由 ~5% 降到 ~0.1% System.out.printf("Emm... %s|%s\n", i1, i2); } } } } }
long
變量讀到無效值無效值 是指 歷來沒有設置過的值。
long
變量讀寫不是原子的,會分爲2次4字節操做。
Demo類com.oldratlee.fucking.concurrency.InvalidLongDemo
。
主線程修改long
變量,爲了方便檢查,每次寫入的long
值的高4字節和低4字節是同樣的。在任務線程中讀取long
變量。
任務線程中讀到了高4字節和低4字節不同的long
變量,便是無效值。
mvn compile exec:java -Dexec.mainClass=com.oldratlee.fucking.concurrency.InvalidLongDemo
DEMO:
public class InvalidLongDemo { long count = 0; public static void main(String[] args) { // LoadMaker.makeLoad(); InvalidLongDemo demo = new InvalidLongDemo(); Thread thread = new Thread(demo.getConcurrencyCheckTask()); thread.start(); for (int i = 0; ; i++) { long l = i; demo.count = l << 32 | l; } } ConcurrencyCheckTask getConcurrencyCheckTask() { return new ConcurrencyCheckTask(); } private class ConcurrencyCheckTask implements Runnable { @Override public void run() { int c = 0; for (int i = 0; ; i++) { long l = count; long high = l >>> 32; long low = l & 0xFFFFFFFFL; if (high != low) { c++; System.err.printf("Fuck! Got invalid long!! check time=%s, happen time=%s(%s%%), count value=%s|%s\n", i + 1, c, (float) c / (i + 1) * 100, high, low); } else { // 若是去掉這個輸出,則在個人開發機上沒有觀察到invalid long System.out.printf("Emm... %s|%s\n", high, low); } } } } }
對於單個變量的值修改 給變量添加volatile也是能夠解決此問題的。
十分感謝