結論:happens-before以爲着何時變量操做對你可見。php
咱們知道cpu的運行極快,而讀取主存對於cpu而言有點慢了,在讀取主存的過程當中cpu一直閒着(也沒數據能夠運行),這對資源來講形成極大的浪費。因此慢慢的cpu演變成了多級cache結構,cpu在讀cache的速度比讀內存快了n倍。java
當線程在執行時,會保存臨界資源的副本到私有work memory中,這個memory在cache中,修改這個臨界資源會更新work memory但並不必定馬上刷到主存中,那麼何時應該刷到主存中呢?何時和其餘副本同步?
並且編譯器爲了提升指令執行效率,是能夠對指令重排序的,重排序後指令的執行順序不同,有可能線程2讀取某個變量時,線程1還未進行寫入操做。這就是線程可見性的來源。segmentfault
針對以上兩個問題,JMM給出happens-before通用的規則(注意這僅對java而言,其餘的就布吉島了)app
i = 1; //線程A執行 j = i ; //線程B執行
j 是否等於1呢?假定線程A的操做(i = 1)happens-before線程B的操做(j = i)。
那麼能夠肯定線程B執行後j = 1 必定成立。
若是他們不存在happens-before原則,那麼j = 1 不必定成立。函數
(即便代碼是先執行i=1,而後執行j=i,也不必定j=1,主要看是否符合happens-before)this
在程序運行過程當中,全部的變動會先在寄存器或本地cache中完成,而後纔會被拷貝到主存以跨越內存柵欄(本地或工做內存到主存之間的拷貝動做),此種跨越序列或順序稱爲happens-before。
注:happens-before本質是順序,重點是跨越內存柵欄
一般狀況下,寫操做必需要happens-before讀操做,即寫線程須要在全部讀線程跨越內存柵欄以前完成本身的跨越動做,其所作的變動才能對其餘線程可見。線程
單例模式可能存在問題哦,請看個人文章【單例模式】DCL的問題和解決方法code
能夠看出,若是有兩個線程都執行過synchronized ,那麼符合"管理鎖定規則",那麼咱們能夠線程 singleton即便不加上volatile,也不會影響線程間的可見性對象
public class Singleton { private static Singleton singleton; private Singleton() { } public static Singleton getInstance() { if (singleton == null) { synchronized (Singleton.class) { if (singleton == null) { Singleton temp = null; try { temp = new Singleton(); } catch (Exception e) { } if (temp != null) singleton = temp; } } } return singleton; } }
線程A和線程B要執行的如下代碼,最後結果b=1嗎?(_其中list爲CopyOnWriteArrayList_)排序
線程A | 線程B |
---|---|
a = 1; | list.get(0); |
list.set(1,""); | int b = a; |
執行順序流1:
步驟 | 線程A | 線程B |
---|---|---|
a | a = 1; | |
b | list.set(1,""); | |
c | list.get(0); | |
d | int b = a; |
執行順序流2:
步驟 | 線程A | 線程B |
---|---|---|
a | a = 1; | |
b | list.get(0); | |
c | list.set(1,""); | |
d | int b = a; |
在確線程B是否必定能看到線程A的a變量前,咱們先看看CopyOnWriteArrayList 的源碼:
能夠發現基本get/set都是一個volatile申明的array變量
private transient volatile Object[] array; public E get(int index) { return get(getArray(), index); } final Object[] getArray() { return array; } public E set(int index, E element) { final ReentrantLock lock = this.lock; lock.lock(); try { //TODO xxx setArray(newElements); } finally { lock.unlock(); } final void setArray(Object[] a) { array = a; }
經過咱們的源碼的分析,基本能夠判斷這裏要用到volatile變量規則,
即:對一個volatile變量的寫操做happen—before後面(時間上)對該變量的讀操做。
咱們對執行順序流進行分析:
(步驟a happens-before 步驟b 記爲 hb(a,b))
順序流1:
根據程序次序規則能夠獲得 hb(a,b),hb(c,d),若是咱們但願b=1,那麼只須要 hb(b,c)
因爲volatile變量規則,咱們能夠獲得hb(b,c),因此必定b=1。
順序流2:
根據程序次序規則能夠獲得 hb(a,c),hb(b,d),若是咱們但願b=1,那麼咱們須要hb(a,b)或hb(c,d)。 然而沒有規則能夠獲得以上條件,故不成立,b不必定等於1。