http://www.cnblogs.com/dolphin0520/p/3920373.html 看完這位大神的分析,依然有一些問題。 Section1 問題 多線程程式設計時,要想保證安全,必須滿足三個條件。 首先複習一下作者提出的三個概念: 1. 原子操作 oracle 官方文檔對atomic access 的定義: In programming, an atomic action is one that effectively happens all at once. An atomic action cannot stop in the middle: it either happens completely, or it doesn't happen at all. No side effects of an atomic action are visible until the action is complete.
2.可見性 我認為原作者提出的可見性即JAVA官方文檔提出的memory consistency error Memory consistency errors occur when different threads have inconsistent views of what should be the same data. 舉個例子: int count = 0; //Thread 1 count ++; //Thread 2 count --; 線程2不一定能看到線程1 對count的修改,因為當程式運作時,每個線程都會加載一份資料到自己的高速緩存中,并對這些資料進行一些操作,隻有當該線程将修改後的資料寫入主存,且其他線程從主存中重新load了資料,才能看到資料的修改。這也就是原作者所說的可見性。 3.有序性 處理器會對沒有資料依賴的代碼進行重排序,在多線程中可能造成意料之外的後果。 Section2 解決方案
那麼,如何在程式設計時保證1中所說的三個條件呢? 1.如何保證原子操作? 1.首先,JAVA語言保證對變量的讀取和指派(将值賦給變量,而不是将變量賦給變量)是原子性操作。 2.JAVA并發包提供了基本類型的 包裝類, java.util.concurrent.atomic,官 方介紹: http://docs.oracle.com/javase/7/docs/api/ 3.通過給操作共享變量的代碼加上synchronized或者lock鎖。 注意:volatile是不能保證原子性的。 2.如何保證可見性? 1.假如A操作 happens-beforeB操作,則A對變量的修改對B一定是可見的。 happens-before的定義: https://docs.oracle.com/javase/specs/jls/se7/html/jls-17.html#jls-17.4.5 2.使用synchronized和lock鎖。 3.重點來了,volatile能保證可見性嗎?在海子大大對volatile是否能保證原子性的描述裡,有這麼一段:
對于可見性,Java提供了volatile關鍵字來保證可見性。
當一個共享變量被volatile修飾時,它會保證修改的值會立即被更新到主存,當有其他線程需要讀取時,它會去記憶體中讀取新值。
然後又舉了這麼一個例子:
假如某個時刻變量inc的值為10,
線程1對變量進行自增操作,線程1先讀取了變量inc的原始值,然後線程1被阻塞了;
然後線程2對變量進行自增操作,線程2也去讀取變量inc的原始值,由于線程1隻是對變量inc進行讀取操作,而沒有對變量進行修改操作,是以不會導緻線程2的工作記憶體中緩存變量inc的緩存行無效,是以線程2會直接去主存讀取inc的值,發現inc的值時10,然後進行加1操作,并把11寫入工作記憶體,最後寫入主存。
然後線程1接着進行加1操作,由于已經讀取了inc的值,注意此時線上程1的工作記憶體中inc的值仍然為10,是以線程1對inc進行加1操作後inc的值為11,然後将11寫入工作記憶體,最後寫入主存。
那麼兩個線程分别進行了一次自增操作後,inc隻增加了1。
解釋到這裡,可能有朋友會有疑問,不對啊,前面不是保證一個變量在修改volatile變量時,會讓緩存行無效嗎?然後其他線程去讀就會讀到新的值,對,這個沒錯。這個就是上面的happens-before規則中的volatile變量規則,但是要注意,線程1對變量進行讀取操作之後,被阻塞了的話,并沒有對inc值進行修改。然後雖然volatile能保證線程2對變量inc的值讀取是從記憶體中讀取的,但是線程1沒有進行修改,是以線程2根本就不會看到修改的值。
根源就在這裡,自增操作不是原子性操作,而且volatile也無法保證對變量的任何操作都是原子性的。
簡化為三步:
第一步,線程1讀取inc的值,然後被阻塞;
第二步,線程2讀取Inc的值,為10,加1,寫入主存;
第三步,線程1接着進行加1操作;
問題1:不是說volatile變量被修改會通知其他線程緩存的變量無效嗎?為什麼線程2修改之後,線程1中的緩存依然有效?難道線程1不應該再次去主存讀取嗎?
問題2:有沒有可能線程1和2同時對inc做出了修改,當寫入主存時,以哪個線程的值為準呢?還是說随機?
問題3:如果說即使線程2對inc做出了修改,線程1依然拿着自己緩存的inc在操作,那還能說volatile變量能保證可見性嗎?是否隻能說volatile盡量保證最大程度的可見,但不完全保證?
而在Java的官方文檔中,也說明了volatile隻是reduce the risk of memory consistency error,見http://docs.oracle.com/javase/tutorial/essential/concurrency/atomic.html。
3.如何保證有序性
1.同樣,happends-before保證有序性;
2.synchronized和lock鎖也能保證有序性
3.重點來了,volatile能保證有序性嗎?
在happens-before的定義中,有這麼一條,A write to a
volatile
field happens-before every subsequent read of that field.
海子大大的原文中說的是:
volatile關鍵字禁止指令重排序有兩層意思:
1)當程式執行到volatile變量的讀操作或者寫操作時,在其前面的操作的更改肯定全部已經進行,且結果已經對後面的操作可見;在其後面的操作肯定還沒有進行;
2)在進行指令優化時,不能将在對volatile變量通路的語句放在其後面執行,也不能把volatile變量後面的語句放到其前面執行。
問題:volatile讀操作是否也能禁止處理器對指令進行重排序嗎?
Section2 總結
總結:按我的了解,volatile應該能達到兩種效果:
1.一定程度上保證了不同線程對這個變量進行操作時的可見性,即一個線程修改了某個變量的值,這新值對其他從主存中讀取該值的線程來說是可見的。
2.當對volatile變量進行寫操作,處理器無法進行重排序。