單例模式是設計模式之一
設計模式,就像古代打仗,我們都聽過孫子兵法,把計謀概括下來弄成一種模式,形成一種套路。
軟件開發中也有很多場景,多數類似的問題場景,解決方案就形成固定的模式,單例模式就是其中一種
單例模式就是保證在某個類中只存在唯一一份實例,不會創建出多個實例
單例模式實現方式有很多,最常見的就是餓漢與懶漢兩種模式,區別就是創建實例的時機不同
餓漢模式
餓漢模式是在類加載的時候創建
class Singleton{private static Singleton singleton=new Singleton();private Singleton(){}public static Singleton GetInstance(){return singleton;}
}
寫一個具體的示例來看:
public class ThreadDemo1 {public static void main(String[] args) throws InterruptedException {Thread[] thread=new Thread[10];for(int i=0;i<5;i++){thread[i]=new Thread(()->{System.out.println(Singleton.GetInstance());});thread[i].start();}for (int i = 0; i < 5; i++) {thread[i].join();}}
}
class Singleton{private static Singleton singleton=new Singleton();private Singleton(){}public static Singleton GetInstance(){return singleton;}
}
根據運行結果我們能看到不同線程都是同一個實例?
懶漢模式
懶漢模式是在第一次使用的時候創建實例
單線程
class Singleton{private static Singleton instance=null;private Singleton (){}public static Singleton GetInstance(){if(instance==null) instance=new Singleton();return instance;}
}
多線程
在多線程情況下再使用單線程的懶漢模式是可能會出現線程不安全的,如果多個線程同時調用GetInstance 方法,可能就會出現多個實例
例如:
public class ThreadDemo2 {public static void main(String[] args) throws InterruptedException {Thread[] thread=new Thread[40];for(int i=0;i<40;i++){thread[i]=new Thread(()->{System.out.println(Singleton.GetInstance());});thread[i].start();}for(int i=0;i<40;i++){thread[i].join();}}
}
class Singleton{private static Singleton instance=null;private Singleton (){}public static Singleton GetInstance(){if(instance==null) instance=new Singleton();return instance;}
}
?在GetInstance 方法中加一個 synchronized 就能解決問題
public class ThreadDemo2 {public static void main(String[] args) throws InterruptedException {Thread[] thread=new Thread[40];for(int i=0;i<40;i++){thread[i]=new Thread(()->{System.out.println(Singleton.GetInstance());});thread[i].start();}for(int i=0;i<40;i++){thread[i].join();}}
}
class Singleton{private static Singleton instance=null;private Singleton (){}public static synchronized Singleton GetInstance(){if(instance==null) instance=new Singleton();return instance;}
}
?改進
加 synchronized 關鍵字確實解決了出現多個實例的問題,但是加鎖與解鎖是開銷比較大的事,這里出現的線程不安全只發生在第一次創建實例時,在經過第一次創建實例后,后面就不需要加鎖了
因此我們可以再加一個 if 判斷一下
在給 instance 變量加上 volatile 關鍵字避免出現內存可見性的線程不安全
public class ThreadDemo2 {public static void main(String[] args) throws InterruptedException {Thread[] thread=new Thread[40];for(int i=0;i<40;i++){thread[i]=new Thread(()->{System.out.println(Singleton.GetInstance());});thread[i].start();}for(int i=0;i<40;i++){thread[i].join();}}
}
class Singleton{private static volatile Singleton instance=null;private Singleton (){}public static Singleton GetInstance(){if(instance==null){synchronized (Singleton.class){if(instance==null) instance=new Singleton();}}return instance;}
}
在 thread[1] 剛判斷是否為空以后,thread[2] 也調用此方法并執行完,再執行thread[1] 后續加鎖操作,這樣也會創建多個實例