Java併發編程之驗證volatile不能保證原子性java
經過系列文章的學習,凱哥已經介紹了volatile的三大特性。1:保證可見性 2:不保證原子性 3:保證順序。那麼怎麼來驗證可見性呢?本文凱哥(凱哥Java:kaigejava)將經過代碼演示來證實爲何說volatile不可以保證共享變量的原子性操做。數據庫
中午去食堂打飯,假設你很是很是的飢餓,須要一葷兩素再加一份米飯。若是食堂打飯的阿姨再給你打一個菜的時候,被其餘人打斷了,給其餘人打飯,而後再回過頭給你打飯。你選一葷兩素再加一份米飯打完的過程被打斷了四次耗時30分鐘。你想一想你本身的感覺。是否是要瘋了,要暴走了!其實,若是把從你點菜到阿姨給你打完飯這個過程,看着計算機的一個線程執行過程的話,那麼在你點菜到你拿到飯菜這個過程是一個完整的,不能被打斷的,這就是所謂的原子性。若是被屢次打斷的話想一想你的心理,就知道程序若是在執行過程被打斷後的結果了。編程
所謂的原子性操做就是線程對變量的操做一旦開始,就會一直運行直到結束。中介不會由於其餘緣由而切換到另外一個線程。操做是不可分割的,在執行完畢以前是不會被其餘任務或是事件中斷的。一個操做或者是多個操做要麼執行都成功要麼執行都失敗(能夠結合數據庫的原子性理解)。併發
模擬場景:ide
共享變量volatile int number=0;執行number++操做。使用多個線程屢次調用。看看使用volatile修飾的number在執行結束後的結果是不是咱們預期的結果。學習
咱們分別用10個線程執行100次,50個線程執行1000次以及50個線程執行一百萬次來看看結果。atom
先來看看變量是用volatil修飾的spa
再來看看主線程裏面:
線程
按照上面我們規定的線程數量運行次數來看看我們預期結果和實際運行結果:
3d
咱們分別用10個線程執行100次,50個線程執行1000次以及50個線程執行一百萬次來
線程數量 |
執行次數 |
number預期結果 |
實際運行結果 |
10 |
100 |
10*100=1000 |
1000 |
50 |
1000 |
五萬 |
49297 |
200 |
1000 |
二十萬 |
194181 |
50 |
1000000 |
5千萬 |
7246921 |
從上面表格中咱們能夠看到,即時共享變量用volatile修飾了。可是隨着線程數量或者執行次數的增長,實際運行結果與預期結果相差愈來愈大。若是預期結果和運行結果一致則說明保證了原子性,可是從結果來看不是這樣的。從而證實了volatile的第二個特性:不能保證原子性。
咱們來分析:
正常來講200個線程,每一個線程執行了1000次。最後應該輸出的是:200*1000=20000.二十萬。可是實際結果卻不是二十萬次。那說明了什麼呢?請看下圖:
說明:
主內存中有共享變量number的值是0,如今有4個CPU帶着4個線程都從主內存中copy變量到本身的工做區。這個是CPU1先競爭到而後再線程1的工做區中執行了number++.執行後將number的值更新成了1,寫回到主內存中了。這個時候正要或者正在通知其餘CPU主內存中的number值變化了。CPU2和CPU3都收到通知了,將本身工做區的變量置爲無效,從新從主內存獲取到number=1的值。這個時候CPU4執行的也快,在尚未收到CPU1的通知的時候,就將本身運行後的number++的值也寫回到了主內存中。其實這個時候,cpu1線程1的操做還在進行中,可是由於cpu4線程4的操做打斷了線程1的操做。第一輪運行結果應該是4,可是由於線程4把線程1執行打斷了,將線程1執行結果覆蓋了。因此實際執行後的效果有多是3或者2可是不多是4.
從上分析結果,咱們更能理解到volatile修飾的共享變量不能保證原子性了。由於有可能被其餘線程打斷執行。
怎麼解決原子性問題呢?可使用juc包下的atomic包下的對象就能夠了。
Volatile的有序性證實,歡迎學習下一篇:《Java併發編程之驗證volatile指令重排-理論篇》
歡迎關注凱哥公衆號:凱哥Java(kaigejava)