voliate關鍵字的應用誤區

寫下這篇博客也是因爲本人之前對voliate關鍵字理解不透徹,纔有了應用誤區,希望同樣沒有理解到位的朋友可以一起踩坑,也歡迎上帝視角明明白白的大佬指出本文的不當之處。

先說一下自己之前對voliate的理解,voliate通過內存屏障可以禁止指令重排序和保證可見性,但是不能保證併發安全。禁止指令重排序就不說了,主要說一說如何保證可見性以及爲什麼不能保證併發安全。

首先理解一下一個線程如何去修改變量a的值,有三步:

第一步從主內存中讀取a的值到該線程的工作內存。

第二步在工作內存中修改a。

第三步將a的值刷回主內存。

被voliate修飾的變量在進行寫操作時,處理器會多一條Lock指令,Lock指令會將修改後的值直接寫進主內存。同時還會讓其他線程的工作內存中緩存的a的值失效,這兩點就可以保證了可見性,我理解的可見性是指所有線程修改a之前a的值都和主內存中一樣,都是一致的。

舉個栗子如果有兩個thread同時操作voliate修飾變量a,thread1和thread2都將a的值從主內存中讀取到工作內存中之後,thread2對a做了修改之後會使thread1工作內存中a的值失效,同時及時地將a的值刷新進主內存,這時候thread1想去修改a發現自己工作內存中的值失效了,就會去主內存中重新讀取,這樣就避免了典型的併發問題。日常voliate的應用場景中喜歡用來修飾全局變量,曾經以爲這樣就可以避免該全局變量併發問題,其實不是的。

因爲voliate不能將一個非原子性的操作變爲原子性。所謂原子性的操作是指該操作在執行期間不受其他操作影響。上面這個對例子中對變量a的值做修改這就不是一個原子性操作,因爲這個操作有三步。

還是剛剛那個例子加深一下理解。thread1如果在第一步和第二步中間a的值被thread2修改了,這時候thread1應該在thread2修改之後的a的基礎上再做修改,總之thread1的操作結果就要受其他線程影響了。重點來了!voliate可以保證thread1的操作結果正確,但是它不能阻止thread2去影響thread1的操作結果。這就是voliate不能將一個非原子性的操作變爲原子性,但是加鎖(該變量只能同時被一個線程操作)或者CAS無鎖可以,這是題外話。

說了這麼多還是沒有說爲什麼voliate不能保證併發安全。先看個情景,還是上面這個例子,剛說thread1在第一步和第二步之間的時候thread2把a修改了,這時候voliate棒棒的一系列騷操作讓thread1在執行第二步真正去修改a的時候拿到的還是最新的即thread2修改之後刷新回主內存的值。但是如果thread1在第二步和第三步之間的時候thread2把a修改了呢!這時候thread1已經把a修改完了,已經準備刷回主內存了,這裏大家不要和我一樣鑽牛角尖,之前我也在想,thread2把a修改了刷回主內存那thread1工作內存裏a的值不就失效了?thread1已經修改完了就不會再去主內存中讀a的值再修改一遍了,它現在要做的是把剛剛修改好a的值也刷回主內存,哦吼,這樣一刷就出問題了。這就是voliate不能保證原子性也就是爲什麼不能保證併發安全的原因。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章