談談Java中Volatile關鍵字的理解
volatile這個關鍵字可能很多朋友都聽說過,或許也都用過。在Java 5之前,它是一個備受爭議的關鍵字,因為在程序中使用它往往會導致出人意料的結果。在Java 5之后,volatile關鍵字才得以重獲生機。volatile關鍵字雖然從字面上理解起來比較簡單,但是要用好不是一件容易的事情。
一、前言
JMM提供了volatile變量定義、final、synchronized塊來保證可見性。
用volatile修飾的變量,線程在每次使用變量的時候,都會讀取變量修改后的最的值。volatile很容易被誤用,用來進行原子性操作。寫了幾個測試的例子,大家可以試一試。
二、主程序
public class Main{ public static void main(String[] args) throws InterruptedException{ List<Thread> threadList = new ArrayList<Thread>(); for(int i=0; i<10; ++i){ Thread thread = new Thread(new Runnable() { @Override public void run() { Single.Holder.instance.add(); } }); threadList.add(thread); thread.start(); } for(Thread thread : threadList) thread.join(); System.out.println(Single.Holder.instance.x); } }
三、單例模式測試
1、沒有volatile,沒有synchronized的情況
class Single{ public int x = 0; public void add(){ try { TimeUnit.MILLISECONDS.sleep(50); } catch (InterruptedException e) { e.printStackTrace(); } ++this.x; } public static class Holder{ public static Single instance = new Single(); } }
輸出結果:8, 9, 10都出現(xiàn)過??梢远噙\行,多試一試,就會發(fā)現(xiàn)不同的結果。
2、有volatile,沒有synchronized
class Single{ public volatile int x = 0; public void add(){ try { TimeUnit.MILLISECONDS.sleep(50); } catch (InterruptedException e) { e.printStackTrace(); } ++this.x; } public static class Holder{ public static Single instance = new Single(); } }
輸出結果:最多出現(xiàn)的是9 和 10。
3、沒有volatile,有synchronized
class Single{ public int x = 0; public synchronized void add(){ try { TimeUnit.MILLISECONDS.sleep(50); } catch (InterruptedException e) { e.printStackTrace(); } ++this.x; } public static class Holder{ public static Single instance = new Single(); } }
輸出結果:無論運行多少次都是10。
四、關于volatile在DCL(double check lock)中的應用
public class LazySingleton { private int someField; private static LazySingleton instance; private LazySingleton() { this.someField = new Random().nextInt(200)+1; // (1) } public static LazySingleton getInstance() { if (instance == null) { // (2) synchronized(LazySingleton.class) { // (3) if (instance == null) { // (4) instance = new LazySingleton(); // (5) } } } return instance; // (6) } public int getSomeField() { return this.someField; // (7) } }
首先說明一下,為什么這種寫法在java中是行不通的!
假設線程Ⅰ是初次調用getInstance()方法,緊接著線程Ⅱ也調用了getInstance()方法和getSomeField()方法,我們要說明的是線程Ⅰ的語句(1)并不happen-before線程Ⅱ的語句(7)。線程Ⅱ在執(zhí)行getInstance()方法的語句(2)時,由于對instance的訪問并沒有處于同步塊中,因此線程Ⅱ可能觀察到也可能觀察不到線程Ⅰ在語句(5)時對instance的寫入,也就是說instance的值可能為空也可能為非空。我們先假設instance的值非空,也就觀察到了線程Ⅰ對instance的寫入,這時線程Ⅱ就會執(zhí)行語句(6)直接返回這個instance的值,然后對這個instance調用getSomeField()方法,該方法也是在沒有任何同步情況被調用,因此整個線程Ⅱ的操作都是在沒有同步的情況下調用 ,這說明線程Ⅰ的語句(1)和線程Ⅱ的語句(7)之間并不存在happen-before關系,這就意味著線程Ⅱ在執(zhí)行語句(7)完全有可能觀測不到線程Ⅰ在語句(1)處對someFiled寫入的值,這就是DCL的問題所在。很荒謬,是吧?DCL原本是為了逃避同步,它達到了這個目的,也正是因為如此,它最終受到懲罰,這樣的程序存在嚴重的bug,雖然這種bug被發(fā)現(xiàn)的概率絕對比中彩票的概率還要低得多,而且是轉瞬即逝,更可怕的是,即使發(fā)生了你也不會想到是DCL所引起的。
我的理解是:線程I 和線程II 都有自己的工作存儲,線程I 創(chuàng)建好了instance后,向內存刷新的時間是不確定的,所以線程Ⅱ在執(zhí)行語句(7)完全有可能觀測不到線程Ⅰ在語句(1)處對someFiled寫入的值。
那么由于在java 5中多增加了一條happen-before規(guī)則:
•對volatile字段的寫操作happen-before后續(xù)的對同一個字段的讀操作。
利用這條規(guī)則我們可以將instance聲明為volatile,即: private volatile static LazySingleton instance;
根據(jù)這條規(guī)則,我們可以得到,線程Ⅰ的語句(5) -> 語線程Ⅱ的句(2) (也就是線程),根據(jù)單線程規(guī)則,線程Ⅰ的語句(1) -> 線程Ⅰ的語句(5)和語線程Ⅱ的句(2) -> 語線程Ⅱ的句(7),再根據(jù)傳遞規(guī)則就有線程Ⅰ的語句(1) -> 語線程Ⅱ的句(7),這表示線程Ⅱ能夠觀察到線程Ⅰ在語句(1)時對someFiled的寫入值,程序能夠得到正確的行為。
補充:在java5之前對final字段的同步語義和其它變量沒有什么區(qū)別,在java5中,final變量一旦在構造函數(shù)中設置完成(前提是在構造函數(shù)中沒有泄露this引用),其它線程必定會看到在構造函數(shù)中設置的值。而DCL的問題正好在于看到對象的成員變量的默認值,因此我們可以將LazySingleton的someField變量設置成final,這樣在java5中就能夠正確運行了。
以上內容是小編給大家介紹的Java中Volatile關鍵字的知識,希望對大家有所幫助!
相關文章
SpringBoot RestTemplate 簡單包裝解析
這篇文章主要介紹了SpringBoot RestTemplate 簡單包裝解析,文中通過示例代碼介紹的非常詳細,對大家的學習或者工作具有一定的參考學習價值,需要的朋友可以參考下2019-08-08Spring-AOP @AspectJ切點函數(shù)之@annotation()用法
這篇文章主要介紹了Spring-AOP @AspectJ切點函數(shù)之@annotation()用法,具有很好的參考價值,希望對大家有所幫助。如有錯誤或未考慮完全的地方,望不吝賜教2021-07-07Spring Cloud下OAUTH2注銷的實現(xiàn)示例
本篇文章主要介紹了Spring Cloud下OAUTH2注銷的實現(xiàn)示例,小編覺得挺不錯的,現(xiàn)在分享給大家,也給大家做個參考。一起跟隨小編過來看看吧2018-03-03Spring?Boot項目抵御XSS攻擊實戰(zhàn)過程
XSS攻擊又稱跨站腳本攻擊,通常指利用網(wǎng)頁開發(fā)時留下的漏洞,通過巧妙的方法注入惡意指令代碼到網(wǎng)頁,使用戶加載并執(zhí)行攻擊者惡意制造的網(wǎng)頁程序,下面這篇文章主要給大家介紹了關于Spring?Boot項目抵御XSS攻擊的相關資料,需要的朋友可以參考下2022-11-11超簡單的java獲取鼠標點擊位置坐標的實例(鼠標在Jframe上的坐標)
在Java窗體Jframe上獲取鼠標點擊的坐標,其中使用了匿名內部類,實例代碼非常簡單易懂,大家可以學習一下2018-03-03