一、簡介
單例模式,指的是某一個類,只允許實例出一個對象存在。而實現單例模式有懶漢式和餓漢式。餓漢式指的是在創建類時就初始化好對象,,而懶漢式指的是在需要使用到對象實例時,才進行初始化對象。
二、實現方式
餓漢式:
/**
* 餓漢式單例模式
*/
public class HungerSingleton {
private static HungerSingleton instance=new HungerSingleton();
private HungerSingleton(){}
public static HungerSingleton getInstance(){
return instance;
}
}
懶漢式(不考慮線程安全):
/**
* 懶漢式單例
*/
public class LazySingleton {
private static LazySingleton instance=null;
private LazySingleton(){
System.out.println("初始化LazySingleton..........");
}
public static LazySingleton getInstance(){
if(instance==null)
instance=new LazySingleton();
return instance;
}
}
三、懶漢式的線程不安全
懶漢式創建單例,在需要時才創建對象,因此在空間上更加友好。但也存在多線程的問題,比如線程1調用getInstance執行到
instance=new LazySingleton();
線程2也調用getInstance,並且判斷出instance==null,也同樣執行
instance=new LazySingleton();
這就導致了多線程下可能創建多個對象,這就不符合我們的預期了,測試代碼如下:
public class Singleton {
public static void main(String[] args) {
for(int i=0;i<20;i++){
new Thread(new Runnable() {
public void run() {
LazySingleton.getInstance();
}
}).start();
}
}
}
四、DCL雙端檢測鎖
使用雙端檢測鎖,解決這種問題,代碼如下:
/**
* 懶漢式單例
*/
public class LazySingletonNew {
private static LazySingletonNew instance=null;
private LazySingletonNew(){
System.out.println("初始化LazySingletonNew..........");
}
public static LazySingletonNew getInstance(){
if(instance==null){
synchronized (LazySingletonNew.class){
if(instance==null)
instance=new LazySingletonNew();
}
}
return instance;
}
}
測試代碼如下:
public class Singleton {
public static void main(String[] args) {
for(int i=0;i<20;i++){
new Thread(new Runnable() {
public void run() {
LazySingleton.getInstance();
}
}).start();
}
for(int i=0;i<20;i++){
new Thread(new Runnable() {
public void run() {
LazySingletonNew.getInstance();
}
}).start();
}
}
}
五、使用volatile+DCL
使用上述的DCL,似乎已經可以解決線程安全問題,而實際上上面的DCL仍然有出現問題的可能性,雖然說很小,分析如下:
instance=new LazySingletonNew();並非是原子操作,new創建對象分爲三步:
1、內存中分配一段空間
2、初始化該空間
3、將instance指向該空間
而由於指令可能存在重排(編譯器優化或者cpu優化),可能執行的順序爲1、 3、 2、
那麼,可能存在如下:
線程1創建空間,在synchronize代碼塊中,new對象的指令順序爲1、 3、 2,並且未執行到3時由於線程調度切換爲線程2,線程2再次判斷if(instance==null),發現不爲null,因此將未初始化的空間拿來用了。這時,調用一個未初始化的空間裏面的方法,變量名等將可能導致錯誤。
因此,使用volatile禁止指令重排。
代碼如下:
/**
* 懶漢式單例
*/
public class LazySingletonNew {
private static volatile LazySingletonNew instance=null;
private LazySingletonNew(){
System.out.println("初始化LazySingletonNew..........");
}
public static LazySingletonNew getInstance(){
if(instance==null){
synchronized (LazySingletonNew.class){
if(instance==null)
instance=new LazySingletonNew();
}
}
return instance;
}
}