談?wù)凧ava中Volatile關(guān)鍵字的理解
volatile這個(gè)關(guān)鍵字可能很多朋友都聽(tīng)說(shuō)過(guò),或許也都用過(guò)。在Java 5之前,它是一個(gè)備受爭(zhēng)議的關(guān)鍵字,因?yàn)樵诔绦蛑惺褂盟鶗?huì)導(dǎo)致出人意料的結(jié)果。在Java 5之后,volatile關(guān)鍵字才得以重獲生機(jī)。volatile關(guān)鍵字雖然從字面上理解起來(lái)比較簡(jiǎn)單,但是要用好不是一件容易的事情。
一、前言
JMM提供了volatile變量定義、final、synchronized塊來(lái)保證可見(jiàn)性。
用volatile修飾的變量,線(xiàn)程在每次使用變量的時(shí)候,都會(huì)讀取變量修改后的最的值。volatile很容易被誤用,用來(lái)進(jìn)行原子性操作。寫(xiě)了幾個(gè)測(cè)試的例子,大家可以試一試。
二、主程序
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); } }
三、單例模式測(cè)試
1、沒(méi)有volatile,沒(méi)有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(); } }
輸出結(jié)果:8, 9, 10都出現(xiàn)過(guò)。可以多運(yùn)行,多試一試,就會(huì)發(fā)現(xiàn)不同的結(jié)果。
2、有volatile,沒(méi)有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(); } }
輸出結(jié)果:最多出現(xiàn)的是9 和 10。
3、沒(méi)有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(); } }
輸出結(jié)果:無(wú)論運(yùn)行多少次都是10。
四、關(guān)于volatile在DCL(double check lock)中的應(yīng)用
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) } }
首先說(shuō)明一下,為什么這種寫(xiě)法在java中是行不通的!
假設(shè)線(xiàn)程Ⅰ是初次調(diào)用getInstance()方法,緊接著線(xiàn)程Ⅱ也調(diào)用了getInstance()方法和getSomeField()方法,我們要說(shuō)明的是線(xiàn)程Ⅰ的語(yǔ)句(1)并不happen-before線(xiàn)程Ⅱ的語(yǔ)句(7)。線(xiàn)程Ⅱ在執(zhí)行g(shù)etInstance()方法的語(yǔ)句(2)時(shí),由于對(duì)instance的訪(fǎng)問(wèn)并沒(méi)有處于同步塊中,因此線(xiàn)程Ⅱ可能觀察到也可能觀察不到線(xiàn)程Ⅰ在語(yǔ)句(5)時(shí)對(duì)instance的寫(xiě)入,也就是說(shuō)instance的值可能為空也可能為非空。我們先假設(shè)instance的值非空,也就觀察到了線(xiàn)程Ⅰ對(duì)instance的寫(xiě)入,這時(shí)線(xiàn)程Ⅱ就會(huì)執(zhí)行語(yǔ)句(6)直接返回這個(gè)instance的值,然后對(duì)這個(gè)instance調(diào)用getSomeField()方法,該方法也是在沒(méi)有任何同步情況被調(diào)用,因此整個(gè)線(xiàn)程Ⅱ的操作都是在沒(méi)有同步的情況下調(diào)用 ,這說(shuō)明線(xiàn)程Ⅰ的語(yǔ)句(1)和線(xiàn)程Ⅱ的語(yǔ)句(7)之間并不存在happen-before關(guān)系,這就意味著線(xiàn)程Ⅱ在執(zhí)行語(yǔ)句(7)完全有可能觀測(cè)不到線(xiàn)程Ⅰ在語(yǔ)句(1)處對(duì)someFiled寫(xiě)入的值,這就是DCL的問(wèn)題所在。很荒謬,是吧?DCL原本是為了逃避同步,它達(dá)到了這個(gè)目的,也正是因?yàn)槿绱耍罱K受到懲罰,這樣的程序存在嚴(yán)重的bug,雖然這種bug被發(fā)現(xiàn)的概率絕對(duì)比中彩票的概率還要低得多,而且是轉(zhuǎn)瞬即逝,更可怕的是,即使發(fā)生了你也不會(huì)想到是DCL所引起的。
我的理解是:線(xiàn)程I 和線(xiàn)程II 都有自己的工作存儲(chǔ),線(xiàn)程I 創(chuàng)建好了instance后,向內(nèi)存刷新的時(shí)間是不確定的,所以線(xiàn)程Ⅱ在執(zhí)行語(yǔ)句(7)完全有可能觀測(cè)不到線(xiàn)程Ⅰ在語(yǔ)句(1)處對(duì)someFiled寫(xiě)入的值。
那么由于在java 5中多增加了一條happen-before規(guī)則:
•對(duì)volatile字段的寫(xiě)操作happen-before后續(xù)的對(duì)同一個(gè)字段的讀操作。
利用這條規(guī)則我們可以將instance聲明為volatile,即: private volatile static LazySingleton instance;
根據(jù)這條規(guī)則,我們可以得到,線(xiàn)程Ⅰ的語(yǔ)句(5) -> 語(yǔ)線(xiàn)程Ⅱ的句(2) (也就是線(xiàn)程),根據(jù)單線(xiàn)程規(guī)則,線(xiàn)程Ⅰ的語(yǔ)句(1) -> 線(xiàn)程Ⅰ的語(yǔ)句(5)和語(yǔ)線(xiàn)程Ⅱ的句(2) -> 語(yǔ)線(xiàn)程Ⅱ的句(7),再根據(jù)傳遞規(guī)則就有線(xiàn)程Ⅰ的語(yǔ)句(1) -> 語(yǔ)線(xiàn)程Ⅱ的句(7),這表示線(xiàn)程Ⅱ能夠觀察到線(xiàn)程Ⅰ在語(yǔ)句(1)時(shí)對(duì)someFiled的寫(xiě)入值,程序能夠得到正確的行為。
補(bǔ)充:在java5之前對(duì)final字段的同步語(yǔ)義和其它變量沒(méi)有什么區(qū)別,在java5中,final變量一旦在構(gòu)造函數(shù)中設(shè)置完成(前提是在構(gòu)造函數(shù)中沒(méi)有泄露this引用),其它線(xiàn)程必定會(huì)看到在構(gòu)造函數(shù)中設(shè)置的值。而DCL的問(wèn)題正好在于看到對(duì)象的成員變量的默認(rèn)值,因此我們可以將LazySingleton的someField變量設(shè)置成final,這樣在java5中就能夠正確運(yùn)行了。
以上內(nèi)容是小編給大家介紹的Java中Volatile關(guān)鍵字的知識(shí),希望對(duì)大家有所幫助!
- 深入解析Java中volatile關(guān)鍵字的作用
- Java中volatile關(guān)鍵字的作用與用法詳解
- Java中Volatile關(guān)鍵字詳解及代碼示例
- Java中volatile關(guān)鍵字實(shí)現(xiàn)原理
- java多線(xiàn)程編程之慎重使用volatile關(guān)鍵字
- java volatile關(guān)鍵字使用方法及注意事項(xiàng)
- 詳解Java面試官最?lèi)?ài)問(wèn)的volatile關(guān)鍵字
- 詳解Java線(xiàn)程編程中的volatile關(guān)鍵字的作用
- Java里volatile關(guān)鍵字是什么意思
- Java中volatile關(guān)鍵字的作用是什么舉例詳解
相關(guān)文章
SpringBoot RestTemplate 簡(jiǎn)單包裝解析
這篇文章主要介紹了SpringBoot RestTemplate 簡(jiǎn)單包裝解析,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友可以參考下2019-08-08Spring-AOP @AspectJ切點(diǎn)函數(shù)之@annotation()用法
這篇文章主要介紹了Spring-AOP @AspectJ切點(diǎn)函數(shù)之@annotation()用法,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2021-07-07Spring Cloud下OAUTH2注銷(xiāo)的實(shí)現(xiàn)示例
本篇文章主要介紹了Spring Cloud下OAUTH2注銷(xiāo)的實(shí)現(xiàn)示例,小編覺(jué)得挺不錯(cuò)的,現(xiàn)在分享給大家,也給大家做個(gè)參考。一起跟隨小編過(guò)來(lái)看看吧2018-03-03Spring?Boot項(xiàng)目抵御XSS攻擊實(shí)戰(zhàn)過(guò)程
XSS攻擊又稱(chēng)跨站腳本攻擊,通常指利用網(wǎng)頁(yè)開(kāi)發(fā)時(shí)留下的漏洞,通過(guò)巧妙的方法注入惡意指令代碼到網(wǎng)頁(yè),使用戶(hù)加載并執(zhí)行攻擊者惡意制造的網(wǎng)頁(yè)程序,下面這篇文章主要給大家介紹了關(guān)于Spring?Boot項(xiàng)目抵御XSS攻擊的相關(guān)資料,需要的朋友可以參考下2022-11-11Java中HashMap與String字符串互轉(zhuǎn)的問(wèn)題解決
本文介紹了Java中HashMap與String字符串互轉(zhuǎn)的問(wèn)題解決,當(dāng)我們有需求將HashMap轉(zhuǎn)為Json格式的String時(shí),需要使用FastJson/Gson將HashMap轉(zhuǎn)為String,感興趣的可以了解一下2022-03-03超簡(jiǎn)單的java獲取鼠標(biāo)點(diǎn)擊位置坐標(biāo)的實(shí)例(鼠標(biāo)在Jframe上的坐標(biāo))
在Java窗體Jframe上獲取鼠標(biāo)點(diǎn)擊的坐標(biāo),其中使用了匿名內(nèi)部類(lèi),實(shí)例代碼非常簡(jiǎn)單易懂,大家可以學(xué)習(xí)一下2018-03-03