單例模式即代碼中只有一個實例的模式
適用場景:有些場景下,有的類只能有一個對象,不能有多個
要注意:在單例模式下,要保證不能產生多個實例
1、餓漢模式
class Singleton{private static Singleton instance = new Singleton();public static Singleton getSingleton() {return instance;}private Singleton(){}
}public class demo {public static void main(String[] args) {Singleton singleton1 = Singleton.getSingleton();Singleton singleton2 = Singleton.getSingleton();System.out.println(singleton1==singleton2);System.out.println(singleton1.equals(singleton2));}
}
將構造方法設為private,使得不能自己初始化類,?只能用默認給定的類
由于是單例,所以singleton1與singleton2地址內容都是一樣的,兩者是同一個實例
2、 懶漢模式
(1)懶漢模式-不安全
class SingletonLazy{private static SingletonLazy instance = null;public static SingletonLazy getSingletonLazy() {if(instance == null){instance = new SingletonLazy();}return instance;}private SingletonLazy(){}
}public class demo {public static void main(String[] args) {SingletonLazy singletonLazy1 = SingletonLazy.getSingletonLazy();SingletonLazy singletonLazy2 = SingletonLazy.getSingletonLazy();System.out.println(singletonLazy1 == singletonLazy2);System.out.println(singletonLazy1.equals(singletonLazy2));}
}
餓漢模式中,無論原來有沒有創建instance,都會初始化一個新的instance
懶漢模式為了節約開銷,會在getSingletonLazy()方法中判斷instance是否是null。如果不是null,說明instance已經初始化,只須直接返回即可;如果是第一次獲取,那么instance沒有初始化,是null,這時初始化后再返回
(2)懶漢模式-線程安全
上面的代碼存在的問題是:由于判斷if(instance == null)這一步和初始化instance = new SingletonLazy()這一步是分開的,有可能會出現線程安全問題
???????
如上圖,T1和T2都各創建了一個instance實例,從而出現了兩個實例,破壞了單例模式的規則
為了解決上述線程安全問題,我們在getSingletonLazy()方法中對這段代碼進行加鎖(靜態方法代碼塊加鎖對象用類名.class)
public static SingletonLazy getInstance() {synchronized (SingletonLazy.class){if(instance == null){instance = new SingletonLazy();}}return instance;}
(3)空間浪費
改成加鎖代碼后,產生了一個新問題,那就是:每次獲得instance時都要進行加鎖,但是加鎖本身是一項耗費空間資源的操作,這樣便會大大降低代碼性能
如果我們能夠得知實例已經創建,那么就不用再進行加鎖了,所以在加鎖之前我們對instance進行一次判斷
public static SingletonLazy getInstance() {if(instance == null){synchronized (SingletonLazy.class){if(instance == null){instance = new SingletonLazy();}}}return instance;
}
注意,第一次判斷和第二次判斷作用并不一樣:第一次是為了判斷是否需要加鎖;第二次是為了判斷是否需要新創建實例
(4)指令重排序問題
經過兩次優化,上述代碼仍存在問題,那就是——指令重排序問題
?· 什么是指令重排序問題呢
當我們在new一個對象的時候,new這個過程分為三步
1.申請內存空間
2.在內存空間上構造對象 (構造方法)3.把內存的地址,賦值給 instance 引用
這個過程可以按照123來執行,也可以按照132的順序來執行?
在我們上述優化過的代碼中,如果T1線程在new的過程中按照132的順序來執行,那么在執行到第二步時,恰好T2第一次對instance進行判斷,由于instance已經創建了實例,那么T2會直接返回這個沒有構造對象的instance
如果代碼中對返回的instance進行訪問屬性/方法等操作,程序就會報錯
?· volatile
為了解決這個問題,我們使用了volatile關鍵字來對instance變量進行修飾
這樣,在進行new操作時,就不會產生指令重排序問題
class SingletonLazy{private static volatile SingletonLazy instance = null;public static SingletonLazy getInstance() {if (instance == null){synchronized (SingletonLazy.class){if (instance == null){instance = new SingletonLazy();}}}return instance;}private SingletonLazy(){}
}
volatile解決內存可見性問題