深度講解23種設計模式,力爭每種設計模式都刨析到底,廢話不多說,開始第一種設計模式 - 單例,
作者已知的單例模式有8種寫法,而每一種寫法,都有自身的優缺點,
1,使用頻率最高的寫法,廢話不多說,直接上代碼
/**
* @author xujp
* 餓漢式 靜態變數 單例
*/
public class Singleton implements Serializable { private static final long serialVersionUID = 1L; private final static Singleton instance = new Singleton(); private Singleton(){} public static Singleton getSingleton(){ return instance; } private String tmp; public String getTmp() { return tmp; } public void setTmp(String tmp) { this.tmp = tmp; } }
new Singleton() 的執行時機 - > 類加載時
這種方法是最通用的單例實作,也是筆者常用的,但這種方法有一些缺點:
1)記憶體方面,如果單例中的內容很多,會在類加載時,就占用java虛擬機(這里專指HotSpot)空間,
2)序列化以及反序列化問題,如果這個單例類實作了序列化介面Serializable,那么可以通過反序列化來破壞單例,
通過反序列化破壞單例:
public static void main(String[] args) throws IOException, ClassNotFoundException {
Singleton singleton=null;
Singleton singletonNew=null;
singleton=Singleton.getSingleton();
singleton.setTmp("123");
ByteArrayOutputStream bos=new ByteArrayOutputStream();
ObjectOutputStream oos=new ObjectOutputStream(bos);
oos.writeObject(singleton);
ByteArrayInputStream bis=new ByteArrayInputStream(bos.toByteArray());
ObjectInputStream ois=new ObjectInputStream(bis);
singletonNew= (Singleton) ois.readObject();
singleton.setTmp("456");
System.out.println(singletonNew.getTmp());
System.out.println(singleton.getTmp());
System.out.println(singleton==singletonNew);
}
輸出結果為:
false
123
456
從這里例子中我們可以看到單例被破壞了,也就不能保證單例的唯一性,
2,第一種方案的變種
/**
* @author xujp
* 餓漢式 靜態代碼塊 單例
*/
public class Singleton implements Serializable {
private static final long serialVersionUID = 1L;
private final static Singleton instance;
static {
instance = new Singleton();
}
private Singleton(){}
public static Singleton getSingleton(){
return instance;
}
}
其實這種方法和第一種方法,幾乎沒有什么區別,
3,執行緒不安全的寫法 - 1
/**
* @author xujp
* 懶漢式 單例 執行緒不安全
*/
public class Singleton implements Serializable {
private static final long serialVersionUID = 1L;
private static Singleton instance;
private Singleton(){}
public static Singleton getSingleton(){
if(null == instance) {
instance = new Singleton();
}
return instance;
}
}
這種寫法,雖然實作了懶加載,節省了記憶體,但執行緒不安全,
假設有兩個執行緒,并假設 new Singleton() 耗時2秒,0秒時,執行緒1執行new,然后去等待,1秒時,執行緒2執行if判斷,
這個時候判斷結果就是true,這樣就會出現兩個Singleton物件,完美破壞掉了單例,
4,執行緒不安全的寫法 - 2
/**
* @author xujp
* 懶漢式 單例 代碼塊加鎖 執行緒不安全
*/
public class Singleton implements Serializable { private static final long serialVersionUID = 1L; private static Singleton instance; private Singleton(){} public static Singleton getSingleton(){ if(null == instance) { synchronized (Singleton.class) { instance = new Singleton(); } } return instance; } }
這種寫法雖然在new Single()時,增加了鎖,但這個鎖,并不能阻止單例被破壞,所以這種寫法錯誤,
同樣,假設有兩個執行緒,執行緒1執行到synchronized時,執行緒2執行if判斷,這個時候判斷結果就是true,
這樣就會出現兩個Singleton物件,同樣完美破壞掉了單例,
5,執行緒安全,但資源消耗過多
/**
* @author xujp
* 懶漢式 單例 方法加鎖 執行緒不安全
*/
public class Singleton implements Serializable { private static final long serialVersionUID = 1L; private static Singleton instance; private Singleton(){} synchronized public static Singleton getSingleton(){ if(null == instance) { instance = new Singleton(); } return instance; } }
這種寫法確實能夠保證執行緒安全,但synchronized屬于方法鎖,而方法鎖回鎖定物件,導致性能低下,
6,相對完美的寫法 - 1
/**
* @author xujp
* 懶漢式 單例 代碼加鎖 執行緒安全
*/
public class Singleton implements Serializable { private static final long serialVersionUID = 1L; private static volatile Singleton instance; private Singleton(){} public static Singleton getSingleton(){ if(null == instance) { synchronized (Singleton.class) { if(null == instance) { instance = new Singleton(); } } } return instance; } }
雙檢查這種寫法,在多執行緒問題上,屬實沒有問題,synchronized也沒有鎖定物件,而且也優化了鎖資源開銷問題,
7,相對完美的寫法 - 2
/**
* @author xujp
* 懶漢式 單例 靜態內部類 執行緒安全
*/
public class Singleton implements Serializable { private static final long serialVersionUID = 1L; private static class SingletonInstance{ private static Singleton instance = new Singleton(); } private Singleton(){} public static Singleton getSingleton(){ return SingletonInstance.instance; } }
使用靜態內部類來實作單例,主要借助JVM機制,靜態內部類初始化的時候,其他執行緒無法進入,從而避免了多執行緒問題,
而且靜態內部類不會直接初始化,從而減輕了記憶體開銷,
8,完美寫法
/**
* @author xujp
* 列舉實作單例
*/
public enum Singleton { SINGLETON; private String property = "hello ca fe ba be"; public void doSomeThing(){ System.out.println(property); } }
這種寫法用列舉解決多執行緒問題,而且時唯一一種解決序列化問題的寫法,
改寫法出自大神Josh Bloch,如果有興趣可以去查看一下他的資料,
總結:
1,1和2寫法雖然是餓漢式,沒有實作懶加載,也沒有100%保證單例,但卻是我們最常用的寫法,
因為,單例物件通常占用空間不會很大,而且程式都由程式員自己管理,被反序列的危險性不高,
2,3和4寫法實作了懶加載,減少了記憶體開銷,但不能使用,因為多執行緒開發,是我們常見的開發,
3,5寫法使用了方法鎖,會將物件鎖住,會導致性能大打折扣,
4,6和7寫法,懶加載、性能都非常完美,缺點只有一個,那就是序列化問題,
5,8寫法,筆者暫未發現缺點,
實際開發中,無論是使用1、2寫法,還是使用6、7寫法,亦或是使用8寫法,都是可以的,
轉載請註明出處,本文鏈接:https://www.uj5u.com/ruanti/22715.html
標籤:設計模式
上一篇:設計模式--單例
下一篇:圖解Java設計模式之UML類圖
