代碼:
class Singleton{
private volatile static Singleton instance = null;
private Singletion{}
public static Singleton getInstance(){
if(instance==null){ //#1
synchronized(Singleton.class){ //#2
if(instance==null){ //#3
instance = new Singletion(); //#4
}else{
System.out.println("test"); //#5
}
}
}
return instance;
}
}
單例對象爲何要加上volatile關鍵字?有如下兩方面原因:
(1)保證可見性:
如果"private static Singleton instance = null;":
1. thread1進入#1, 這時thread1的instance爲null,thread1讓出CPU資源給thread2.
2. thread2進入#1, 這時thread2的instance爲null,thread2讓出CPU資源給thread1.
3. thread1會依次執行#2,#3,#4,最終在thread1裏面實例化了instance。thread1執行完畢讓出CPU資源給thread2.
4. thread2接着#1跑下去,跑到#3的時候,由於#1裏面拿到的instance還是null(並沒有及時從thread1裏面拿到最新的),所以thread2仍然會執行#3,#4.
5. 最後thread1和thread2都實例化了instance.
如果"private volatile static Singleton instance = null;":
1. thread1進入#1, 這時thread1的instance爲null(java內存模型會從主內存裏拷貝一份instance(null)到thread1),thread1讓出CPU資源給thread2.
2. thread2進入#1, 這時thread2的instance爲null(java內存模型會從主內存裏拷貝一份instance(null)到thread2), thread2讓出CPU資源給thread1.
3. thread1會依次執行#2,#3,#4, 最終在thread1裏面實例化了instance(由於是volatile修飾的變量,會馬上同步到主內存的變量去)。thread1執行完畢讓出CPU資源給thread2.
4. thread2接着#1跑下去,跑到#3的時候,會又一次從主內存拷貝一份instance(!=null)回來,所以thread2就直接跑到了#5.
5. 最後thread2不會再重複實例化instance了.
(2)防止重排序:
在單例模式中,Instance instance= new Instance(); 這一句代碼不是原子操作,它可以分成三步原子指令:
1.分配內存地址;
2.new一個Instance對象;
3.將內存地址賦值給instance;
CPU爲了提高執行效率,這三步操作的順序可以是123,也可以是132。如果是132順序的話,當把內存地址賦給inst後,inst指向的內存地址上面還沒有new出來單例對象,這時候如果就拿到instance的話,它其實就是空的,會報空指針異常。