java volatile 原子性_Java中volatile不能保证原子性的证明
Java并發(fā)編程之驗(yàn)證volatile不能保證原子性
通過(guò)系列文章的學(xué)習(xí),凱哥已經(jīng)介紹了volatile的三大特性。1:保證可見(jiàn)性 2:不保證原子性 3:保證順序。那么怎么來(lái)驗(yàn)證可見(jiàn)性呢?本文凱哥(凱哥Java:kaigejava)將通過(guò)代碼演示來(lái)證明為什么說(shuō)volatile不能夠保證共享變量的原子性操作。
我們來(lái)舉個(gè)現(xiàn)實(shí)生活中的例子:
中午去食堂打飯,假設(shè)你非常非常的饑餓,需要一葷兩素再加一份米飯。如果食堂打飯的阿姨再給你打一個(gè)菜的時(shí)候,被其他人打斷了,給其他人打飯,然后再回過(guò)頭給你打飯。你選一葷兩素再加一份米飯打完的過(guò)程被打斷了四次耗時(shí)30分鐘。你想想你自己的感受。是不是要瘋了,要暴走了!其實(shí),如果把從你點(diǎn)菜到阿姨給你打完飯這個(gè)過(guò)程,看著計(jì)算機(jī)的一個(gè)線程執(zhí)行過(guò)程的話,那么在你點(diǎn)菜到你拿到飯菜這個(gè)過(guò)程是一個(gè)完整的,不能被打斷的,這就是所謂的原子性。如果被多次打斷的話想想你的心理,就知道程序如果在執(zhí)行過(guò)程被打斷后的結(jié)果了。
原子性操作的定義:
所謂的原子性操作就是線程對(duì)變量的操作一旦開(kāi)始,就會(huì)一直運(yùn)行直到結(jié)束。中介不會(huì)因?yàn)槠渌蚨袚Q到另一個(gè)線程。操作是不可分割的,在執(zhí)行完畢之前是不會(huì)被其他任務(wù)或是事件中斷的。一個(gè)操作或者是多個(gè)操作要么執(zhí)行都成功要么執(zhí)行都失敗(可以結(jié)合數(shù)據(jù)庫(kù)的原子性理解)。
怎么證明volatile修飾的共享變量就不能保證原子性呢?
模擬場(chǎng)景:
共享變量volatile int number=0;執(zhí)行number++操作。使用多個(gè)線程多次調(diào)用。看看使用volatile修飾的number在執(zhí)行結(jié)束后的結(jié)果是否是我們預(yù)期的結(jié)果。
我們分別用10個(gè)線程執(zhí)行100次,50個(gè)線程執(zhí)行1000次以及50個(gè)線程執(zhí)行一百萬(wàn)次來(lái)看看結(jié)果。
先來(lái)看看變量是用volatil修飾的
再來(lái)看看主線程里面:
按照上面咱們規(guī)定的線程數(shù)量運(yùn)行次數(shù)來(lái)看看咱們預(yù)期結(jié)果和實(shí)際運(yùn)行結(jié)果:
我們分別用10個(gè)線程執(zhí)行100次,50個(gè)線程執(zhí)行1000次以及50個(gè)線程執(zhí)行一百萬(wàn)次來(lái)
從上面表格中我們可以看到,即時(shí)共享變量用volatile修飾了。但是隨著線程數(shù)量或者執(zhí)行次數(shù)的增加,實(shí)際運(yùn)行結(jié)果與預(yù)期結(jié)果相差越來(lái)越大。如果預(yù)期結(jié)果和運(yùn)行結(jié)果一致則說(shuō)明保證了原子性,但是從結(jié)果來(lái)看不是這樣的。從而證明了volatile的第二個(gè)特性:不能保證原子性。
為什么從i++的運(yùn)行結(jié)果上就能看出不保證原子性呢?
我們來(lái)分析:
正常來(lái)說(shuō)200個(gè)線程,每個(gè)線程執(zhí)行了1000次。最后應(yīng)該輸出的是:200*1000=20000.二十萬(wàn)。但是實(shí)際結(jié)果卻不是二十萬(wàn)次。那說(shuō)明了什么呢?請(qǐng)看下圖:
說(shuō)明:
主內(nèi)存中有共享變量number的值是0,現(xiàn)在有4個(gè)CPU帶著4個(gè)線程都從主內(nèi)存中copy變量到自己的工作區(qū)。這個(gè)是CPU1先競(jìng)爭(zhēng)到然后再線程1的工作區(qū)中執(zhí)行了number++.執(zhí)行后將number的值更新成了1,寫(xiě)回到主內(nèi)存中了。這個(gè)時(shí)候正要或者正在通知其他CPU主內(nèi)存中的number值變化了。CPU2和CPU3都收到通知了,將自己工作區(qū)的變量置為無(wú)效,重新從主內(nèi)存獲取到number=1的值。這個(gè)時(shí)候CPU4執(zhí)行的也快,在還沒(méi)有收到CPU1的通知的時(shí)候,就將自己運(yùn)行后的number++的值也寫(xiě)回到了主內(nèi)存中。其實(shí)這個(gè)時(shí)候,cpu1線程1的操作還在進(jìn)行中,但是因?yàn)閏pu4線程4的操作打斷了線程1的操作。第一輪運(yùn)行結(jié)果應(yīng)該是4,但是因?yàn)榫€程4把線程1執(zhí)行打斷了,將線程1執(zhí)行結(jié)果覆蓋了。所以實(shí)際執(zhí)行后的效果有可能是3或者2但是不可能是4.
從上分析結(jié)果,我們更能理解到volatile修飾的共享變量不能保證原子性了。因?yàn)橛锌赡鼙黄渌€程打斷執(zhí)行。
怎么解決原子性問(wèn)題呢?可以使用juc包下的atomic包下的對(duì)象就可以了。
Volatile的有序性證明,歡迎學(xué)習(xí)下一篇:《Java并發(fā)編程之驗(yàn)證volatile指令重排-理論篇》
總結(jié)
以上是生活随笔為你收集整理的java volatile 原子性_Java中volatile不能保证原子性的证明的全部?jī)?nèi)容,希望文章能夠幫你解決所遇到的問(wèn)題。
- 上一篇: python中属性是什么意思啊_pyth
- 下一篇: 程序员硬核宝典(面试题集、在线免费工具箱