Singleton:單例模式
先代碼
package h.l.demo.singleton;
/**
*
* @author: Is-Me-Hl
* @date: 2020年3月7日
* @Description: 單例模式測試類
*/
public class Singleton {
public static void main(String[] args) {
Instance instance = Instance.getInstance();
Instance instance2 = Instance.getInstance();
System.out.println("instance == instance2 result :" + (instance == instance2));
Instance3 instance3 = Instance3.getInstance3();
Instance3 instance4 = Instance3.getInstance3();
System.out.println("instance3 == instance4 result :" + (instance3 == instance4));
}
}
/**
*
* @author: Is-Me-Hl
* @date: 2020年3月3日
* @Description: 單例模式:重要一點是構造函數私有,即無法通過new的方式創建對象實例,對外提供唯一的類方法供調用者訪問
*
* 懶漢式,指的是當需要獲取該實例時纔去判斷實例是否存在,不存在再創建。
* 餓漢式:指的是一開始我就給實例化好,需要的時候我就直接給你。
*
* 兩者比較各有優缺點:餓漢式一開始就實例,佔內存,不如懶漢式中等需要時再創建對象的好。但懶漢式也存在問題,獲取實例對象的時候
* 如果在多線程環境中
* ,可能存在線程安全,準確說可能多個線程同時都去獲取了實例,發現沒有該實例,又同時去創建了一個實例,這樣就不是單例
* 的了,需要使用雙層鎖定的方式才能避免,這相對於餓漢式多了這些步驟。
*/
/**
*
* @author: Is-Me-Hl
* @date: 2020年3月4日
* @Description: 單例模式-懶漢式-單線程環境
*/
class Instance {
private static Instance instance;
// 將構造函數私有,不允許通過new的方式創建對象
private Instance() {
}
public static Instance getInstance() {
if (instance == null) {
instance = new Instance();
}
return instance;
}
}
/**
*
* @author: Is-Me-Hl
* @date: 2020年3月4日
* @Description: 單例模式-懶漢式-多線程環境-雙重鎖定-volatile防止指令重排
*/
class Instance2 {
private volatile static Instance2 instance2;
// 創建一個鎖對象
private static Object obj = new Object();
// 將構造函數私有,不允許通過new的方式創建對象
private Instance2() {
}
public static Instance2 getInstance2() {
if (instance2 == null) {
synchronized (obj) {
if (instance2 == null) {
instance2 = new Instance2();
}
}
}
return instance2;
}
}
/**
*
* @author: Is-Me-Hl
* @date: 2020年3月4日
* @Description: 單例模式-餓漢式
*/
class Instance3 {
private static Instance3 instance3 = new Instance3();
// 將構造函數私有,不允許通過new的方式創建對象
private Instance3() {
}
public static Instance3 getInstance3() {
return instance3;
}
}
測試結果:
後分析
- 個人建議:寫代碼是件幸福的事,So,do it
單例模式,定義:保證一個類僅有一個實例,並提供一個訪問它的全局訪問點。實現方式:最好的辦法就是讓類自身負責保存它的唯一實例,這個類可以保證沒有其他實例可以創建,並且它可以提供一個訪問該實例的方法。
什麼時候使用單例模式?當類只允許創建一個實例的時候,即可使用該模式。對於上述demo,單例模式分單線程和多線程的區別,單線程中,無需使用鎖,好比一頓大餐就一個人喫,那需要那麼多的繁文縟節,排隊進餐等。多線程環境就需要上鎖,放置多個線程同時發現實例爲null,去調用實例化方法去同時創建新的實例。使用了volatile是保證實例化過程順序的一致,提供線程安全,防止當線程搶到鎖,剛給該實例分配內存還沒初始化時,其他線程發現該實例不爲null了,就拿該實例對象去執行操作,從而產生錯誤。
看似簡單的一段賦值語句:instance = new Singleton(); 其實JVM內部已經轉換爲多條指令:
memory = allocate(); //1:分配對象的內存空間
ctorInstance(memory); //2:初始化對象
instance = memory; //3:設置instance指向剛分配的內存地址
但是經過重排序後如下:
memory = allocate(); //1:分配對象的內存空間
instance = memory; //3:設置instance指向剛分配的內存地址,此時對象還沒被初始化
ctorInstance(memory); //2:初始化對象
可以看到指令重排之後,instance指向分配好的內存放在了前面,而這段內存的初始化被排在了後面,在線程A初始化完成這段內存之前,線程B雖然進不去同步代碼塊,但是在同步代碼塊之前的判斷就會發現instance不爲空,此時線程B獲得instance對象進行使用就可能發生錯誤。
其他例子:參考自《大話設計模式》有些類也需要計劃生育。
注:以上文章僅是個人總結,若有不當之處,望不吝賜教