代碼清單1java
class SingleInstance{ private static single = null; private SingeInstance(){} public SingleInstance getSingleInstance(){ if(single == null)//0 synchroznied(SingleInstance.class){ if(single == null){ single = new SingleInstance();//1 } } return single; } }
如上代碼的問題是在1處不能保證有序性,即這句代碼其實分爲兩個大的步驟多線程
這個步驟先後是不肯定的。當線程一運行到1處的時候可能會先對象賦值給single了可是此時的single尚未初始化完成。線程2運行的0處的時候會發現這個條件是不符合的因而就返回了single。這時候的single雖然是一個非空的引用,但卻不是一個正確的對象。 這個就是雙重校驗可能出現的問題。併發
可能你據說過JDK1.4之後用volatile修飾變量single能夠解決這個問題,可你知道爲何能解決嗎?spa
volatile的語義是能保持有序性和可見性,可是不能保證原子性線程
什麼是可見性?code
以對象
count = 0; couont++;
爲例這個行代碼的執行過程以下:排序
在多線程的狀況下內存
看到問題了吧,加了兩遍仍是1,出事了啊兄弟!get
volatile內在其中起什麼做用呢?
當用volatile修飾count後這樣線程執行操做的時候也就是上述的****2步驟他不會在副本中取值,而是去主存中取值。
即使是這樣也不能解決計數問題,爲何呢?
volatile的可見性語義是保證線程進行操做也就是上述的步驟2是從主存中取最新的值而不是在本身副本中取值。
在Java內存模型中,容許編譯器和處理器對指令進行重排序,可是重排序過程不會影響到單線程程序的執行,卻會影響到多線程併發執行的正確性。
例子: 代碼清單2
線程A中 context = initContext();//1 flag = true;//2 線程B中 while(!flag){ sleep(100); } dosomething(context);
在單線程中代碼是沒有問題的,可是如代碼清單二中,線程A的代碼可能會發生重排序也就是運行代碼2再運行代碼1這就有問題了。
若是用volatile修飾就會禁止他進行重排序。
原子性簡單來講就是不可分割,若是是原子操做,那它一定是要麼被執行完畢,要麼徹底沒執行兩種狀況之一,不可能出現執行了一部分這種狀況。
volatile字段能保證可見性、禁止重排序,但並不能提供原子性。緣由在於在多線程的條件下,不能保證執行順序,中間會有線程切換的狀況出現。
回到代碼清單1還記得當初的問題嗎?代碼清單1中這個單例有什麼問題咱們已經說過了。怎麼解決呢? 其實有了volatile這個關鍵字就好解決了,在single這個變量上添加volatile就能夠完美解決了。緣由是volatile具備禁止重排序的功能。因此會先進行初始化對象再賦值給變量,0處檢測到的single不爲空的時候就能正確返回single而再也不是一個不完整的single了。