为了防止程序重排序,慎用volatile
??? 之前在InfoQ看到一篇關(guān)于java重排序的一篇文章,覺得里面有些知識(shí)寫得太絕對(duì)了,于是想通過實(shí)際程序來說明一下:
??? 關(guān)于java重排序,這里就不做介紹了,我們知道JVM底層封裝了與OS的交互,它內(nèi)部有自己的一套類似于OS的內(nèi)存模型,程序重排序的設(shè)計(jì)思路基本上是來源于OS跟硬件層面的設(shè)計(jì)。下面直接入正題吧!
??? 我們知道JVM給每個(gè)線程分配了自己的內(nèi)存空間,也就是說在變量存儲(chǔ)方面,分為主內(nèi)存和線程工作內(nèi)存,也就是說,所有線程共享主內(nèi)存,每個(gè)線程都有自己的工作內(nèi)存。程序執(zhí)行的時(shí)候是去工作內(nèi)存里面取值還是去主內(nèi)存里面取值呢?下面以代碼為例:
public?class?DemoWork?{private?boolean?stop=false;private?boolean?start=true;public?void?workThread()?throws?InterruptedException{Thread?workThread=new?Thread(new?Runnable()?{private?int?i=0;@Overridepublic?void?run()?{//?TODO?Auto-generated?method?stubwhile(!stop){i++;/*try?{Thread.sleep(10);}?catch?(InterruptedException?e)?{//?TODO?Auto-generated?catch?blocke.printStackTrace();}*/}start=false;}});workThread.start();Thread.sleep(1000);stop=true;Thread?printThread=new?Thread(new?Runnable()?{private?int?i=0;@Overridepublic?void?run()?{//?TODO?Auto-generated?method?stubwhile(stop&&start){System.out.println("stop?is:"+stop);try?{Thread.sleep(10);}?catch?(InterruptedException?e)?{//?TODO?Auto-generated?catch?blocke.printStackTrace();}}}});printThread.start();}/***?@param?args*?@throws?InterruptedException?*/public?static?void?main(String[]?args)?throws?InterruptedException?{//?TODO?Auto-generated?method?stubDemoWork?dw=new?DemoWork();dw.workThread();}}上面的代碼是不會(huì)停下來的,但是如果把sleep那段代碼的注釋去掉程序就能停下來了,這是什么原因呢?我的理解是:因?yàn)榫€程printThread是能正常執(zhí)行的,所以有兩種可能:
線程workThread里面工作線程stop變量值沒有收到主存的同步,而它一直取的是自己工作線程里面的stop值
主線程更新stop沒有更新主內(nèi)存,以至于主內(nèi)存里面保存的stop值一直是false
? 以上第二點(diǎn)我覺得是可以排除的,因?yàn)榫€程printThread里面的值stop值是true,所以造成以上情況第一點(diǎn)的可能性大一點(diǎn),那為什么把workThread里面的睡眠去掉之后程序又能正常退出呢?那就應(yīng)該是在執(zhí)行這些語句的時(shí)候主內(nèi)存更新了工作內(nèi)存的緣故了(執(zhí)行打印語句也會(huì)推出,至于這里面的原因是什么,暫時(shí)還沒看到相關(guān)的資料,可能跟JVM的重排序規(guī)則有關(guān)系,但是規(guī)則到底是怎樣的呢?),接下來我們來說說volatile。
volatile:
(適用于Java所有版本)讀和寫一個(gè)volatile變量有全局的排序。也就是說每個(gè)線程訪問一個(gè)volatile作用域時(shí)會(huì)在繼續(xù)執(zhí)行之前讀取它的當(dāng)前值,而不是(可能)使用一個(gè)緩存的值。(但是并不保證經(jīng)常讀寫volatile作用域時(shí)讀和寫的相對(duì)順序,也就是說通常這并不是有用的線程構(gòu)建)。
(適用于Java5及其之后的版本)volatile的讀和寫建立了一個(gè)happens-before關(guān)系,類似于申請(qǐng)和釋放一個(gè)互斥鎖[7]。
也就是說在上面workThread線程sleep代碼段注釋的情況下,我們可以使用volatile來修飾stop變量,這樣的話就能強(qiáng)制workThread線程去主內(nèi)存里面取stop的值了,但是這樣做的話在高并發(fā)現(xiàn)會(huì)造成性能問題。之前看了很多的開源代碼,里面解決以上主內(nèi)存與工作內(nèi)存不同步的方式基本上是采用volatile修飾變量解決的。我在想,既然volatile在并發(fā)情況下會(huì)造成性能問題,在workThread循環(huán)快里面執(zhí)行什么類型的代碼快能方便JVM更好的同步主內(nèi)存跟工作內(nèi)存的值,那樣的話,在高并發(fā)下,就能更快的提高程序性能了。
轉(zhuǎn)載于:https://blog.51cto.com/chenyanxi/1577101
總結(jié)
以上是生活随笔為你收集整理的为了防止程序重排序,慎用volatile的全部?jī)?nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: MyBatis知多少(8)关系型数据库
- 下一篇: 酷派S6安全解密之“防窃密防跟踪防监听”