什麼是單例設計模式?
單例模式,是一種常用的軟件設計模式。在它的核心結構中只包含一個被稱爲單例的特殊類。通過單例模式可以保證系統中,應用該模式的類一個類只有一個實例。即一個類只有一個對象實例。
具體實現
需要:
(1)將構造方法私有化,使其不能在類的外部通過new關鍵字實例化該類對象。
(2)在該類內部產生一個唯一的實例化對象,並且將其封裝爲private static類型。
(3)定義一個靜態方法返回這個唯一對象。
實現一:立即加載 / “餓漢模式”
立即加載就是使用類的時候已經將對象創建完畢(不管以後會不會使用到該實例化對象,先創建了再說。很着急的樣子,故又被稱爲“餓漢模式”),常見的實現辦法就是直接new實例化。
package com.Geeksun.singleton;
/**
* “餓漢模式”的優缺點:
*
* 優點:實現起來簡單,沒有多線程同步問題。
*
* 缺點:當類SingletonTest被加載的時候,會初始化static的instance,靜態變量被創建並分配內存空間,從這以後,這個static
* 的instance對象便一直佔着這段內存(即便你還沒有用到這個實例),當類被卸載時,靜態變量被摧毀,並釋放所佔有的內存,
* 因此在某些特定條件下會耗費內存。
*/
public class Singleton01 {
private static Singleton01 instance = new Singleton01();
private Singleton01(){}
public static Singleton01 getInstance(){
return instance;
}
}
實現二:延遲加載 / “懶漢模式”
延遲加載就是調用get()方法時實例才被創建(先不急着實例化出對象,等要用的時候纔給你創建出來。不着急,故又稱爲“懶漢模式”),常見的實現方法就是在get方法中進行new實例化。
package com.Geeksun.singleton;
/**
* “懶漢模式”的優缺點:
*
* 優點:1.在多線程情形下,保證了“懶漢模式”的線程安全。
* 2.實現起來比較簡單,當類SingletonTest被加載的時候,靜態變量static的instance未被創建並分配內存空間,當getInstance方法
* 第一次被調用時,初始化instance變量,並分配內存,因此在某些特定條件下會節約了內存。
*
* 缺點:衆所周知在多線程情形下,synchronized方法通常效率低,顯然這不是最佳的實現方案。
*/
public class Singleton02 {
private static Singleton02 instance;
private Singleton02(){}
public static synchronized Singleton02 getInstance() {
if(instance == null){
instance = new Singleton02();
}
return instance;
}
}
實現三:DCL雙檢查鎖機制(DCL:double checked locking)
package com.Geeksun.singleton;
/**
* “雙重檢查鎖”的優缺點:
*
* 雙重檢查鎖定背後的理論是完美的。不幸地是,現實完全不同。雙重檢查鎖定的問題是:並不能保證它會在單處理器或多處理器計算機上順利運行。
*
* 雙重檢查鎖定失敗的問題並不歸咎於 JVM 中的實現 bug,而是歸咎於 Java 平臺內存模型。內存模型允許所謂的“無序寫入”,這也是這些習語失敗的一個主要原因。
*/
public class Singleton03 {
private volatile static Singleton03 instance;//使用了volatile關鍵字後,重排序被禁止,所有的寫(write)操作都將發生在讀(read)操作之前。
private Singleton03(){}
public static Singleton03 getInstance() {
if(instance == null){
synchronized (Singleton03.class){
if(instance == null){
instance = new Singleton03();
}
}
}
return instance;
}
}
實現四:靜態內部類
package com.Geeksun.singleton;
/**
* 靜態內部類
*註解:定義一個私有的內部類,在第一次用這個嵌套類時,會創建一個實例。而類型爲SingletonHolder的類,只有在
* Singleton.getInstance()中調用,由於私有的屬性,他人無法使用SingleHolder,不調用Singleton.getInstance()就不會創建實例。
* 優點:達到了lazy loading的效果,即按需創建實例。
*
*/
public class Singleton04 {
private Singleton04(){
}
private static class SingletonHolder{
private static final Singleton04 instance = new Singleton04();
}
public Singleton04 getInstance(){
return SingletonHolder.instance;
}
}
實現五:枚舉類
package com.Geeksun.singleton;
/**
* “枚舉類”
* 這種方式是Effective Java作者Josh Bloch 提倡的方式,它不僅能避免多線程同步問題,而且還能防止反序列化重新創建新的對象,
* 可謂是很堅強的壁壘啊,書寫較爲方便。
*
*/
public enum Singleton05 {
INSTANCE;
public void operation(){
}
}
除了枚舉類外,單例模式可以被破解
package com.Geeksun.singleton;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.lang.reflect.Constructor;
public class Client {
public static void main(String[] args) throws Exception {
//通過反射破解單例模式
Singleton01 sc1 = Singleton01.getInstance();
Singleton01 sc2 = Singleton01.getInstance();
Class<Singleton01> clazz = (Class<Singleton01>)Class.forName("com.Geeksun.singleton.Singleton01");
Constructor<Singleton01> c = clazz.getDeclaredConstructor();
c.setAccessible(true);
Singleton01 sc3 = c.newInstance();
Singleton01 sc4 = c.newInstance();
System.out.println(sc1);
System.out.println(sc2);
System.out.println(sc3);
System.out.println(sc4);
//通過反序列化破解單例模式
FileOutputStream fis = new FileOutputStream("D:/test.txt");
ObjectOutputStream oos = new ObjectOutputStream(fis);
oos.writeObject(sc1);
oos.close();
fis.close();
ObjectInputStream ois = new ObjectInputStream(new FileInputStream("d:/test.txt"));
Singleton01 sc5 = (Singleton01)ois.readObject();
System.out.println(sc1);
System.out.println(sc5);
}
}
對於餓漢模式,防止破解的方法是
public class Singleton01 implements Serializable {
private static Singleton01 instance = new Singleton01();
private Singleton01(){
//如果已經創建了對象,則不能調用無參構造函數!
if(instance != null){
throw new RuntimeException();
}
}
public static Singleton01 getInstance(){
return instance;
}
//反序列化時,如果定義了readResolve()則直接返回該方法指定的對象,不需要指定新的對象。
private Object readResolve(){
return instance;
}
}