個人單子模式學習筆記(執行緒安全、雙重檢查、Volitale)
單子模式我已經栽了兩次了,第一次是單子模式沒有考慮執行緒安全,第二次被問到double check 和Volitale。鬼知道下一次我又栽在單子模式的哪兒,所以我決定,見到什麼問題,就都在這兒記著了。
我們先看這種雙重檢查,不加volatile
public static Singleton instance; public static Singleton getInstance() { if (instance == null) //1 { //2 synchronized(Singleton.class) { //3 if (instance == null) //4 instance = new Singleton(); //5 } } return instance; }
這種方式存在什麼問題呢? 也許有人說存在可見性問題:執行緒1執行完第5步,釋放鎖。執行緒2獲得鎖後執行到第4步,由於可見性的原因,發現instance還是null,從而初始化了兩次。 但是不會存在這種情況,因為synchronized能保證執行緒1在釋放鎖之前會講對變數的修改重新整理到主存當中,執行緒2拿到的值是最新的。
實際存在的問題是無序性。 第5步這個new操作是無序的,它可能會被編譯成: - a. 先分配記憶體,讓instance指向這塊記憶體 - b. 在記憶體中建立物件
然而我們需要意識到這麼一個問題,synchronized雖然是互斥的,但不代表一次就把整個過程執行完,它在中間是可能釋放時間片的,時間片不是鎖。(我因為這裡沒轉過來,耽誤了很多時間) 也就是說可能在a執行完後,時間片被釋放,執行緒2執行到1,這時他讀到的instance是不是null呢?(標記1) 基於可見性,可能是null,也可能不是null。 非常奇葩的是
加volatile
public volatile static Singleton instance; public static Singleton getInstance() { if (instance == null) //1 { //2 synchronized(Singleton.class) { //3 if (instance == null) //4 instance = new Singleton(); //5 } } return instance; }
唯一的區別是加了volatile關鍵字,那麼會有什麼現象? 這時要區分jdk版本了,在jdk1.4及之前,volatile並不能保證new操作的有序性,但是它能保證可見性,因此標記1處,讀到的不是null,導致了問題。 從1.5開始,加了volatile關鍵字的引用,它的初始化就不能是: - a. 先分配記憶體,讓instance指向這塊記憶體 - b. 在記憶體中建立物件
而應該是: - a.在記憶體中建立物件 - b.讓instance指向這個物件.
這種形式,也就避免了無序性問題。
什麼情況用Volitale?
待續....