單例設計模式(singleton)最常用、最簡單的設計模式。單例模式的目的是保證在整個應用中某一個類有且只有一個實例(一個類在堆內存只存在一個對象)。怎么樣讓一個類一個類有且只有一個實例呢?最核心的就是一句話就是構造方法私有化。單例模式的編寫有很多種寫法。比如餓漢式、懶漢式、雙重加鎖機制靜態內部類、枚舉。
餓漢式,從名字上理解像是有個人很容易餓,所以他每次不管自己餓不餓,沒事就提前把要吃的東西先準備出來,也就是 “比較勤”,所以實例在初始化的時候就已經建好了,不管你有沒有用到,都先建好了再說。
[1] 構造方法私有化,防止外界通過構造器創建新的工具類對象;
[2] 必須在該類中,自己先創建出一個對象;
[3] 向外暴露一個公共的靜態方法用于返回自身的對象;
// 單例模式(餓漢式)
public class Singleton1 {
// [1]構造方法私有化,防止外界通過構造器創建新的工具類對象
private Singleton1() {
}
// [2] 必須在該類中,自己先創建出一個對象(這行代碼在類加載的時候就執行了)
private static Singleton1 instance = new Singleton1();
// [3] 向外暴露一個公共的靜態方法用于返回自身的對象
public static Singleton1 getSingleton() {
return instance;
}
}
注意:我們知道,類加載的方式是按需加載,且加載一次。因此,在上述單例類被加載時,就會實例化一個對象并交給自己的引用,供系統使用;而且,由于這個類在整個生命周期中只會被加載一次,因此只會創建一個實例,即能夠充分保證單例。
好處:這種寫法比較簡單,就是在類裝載的時候就完成實例化。避免了線程同步問題。
壞處:在類裝載的時候就完成實例化,沒有達到Lazy Loading(懶加載)的效果。 如果從始至終從未使用過這個實例,則會造成內存的浪費。
懶漢式,顧名思義就像一個人比較懶,平時不愛動,事情火燒眉毛了才迫不得已去做,“比較懶”,用的時候才去檢查有沒有實例,如果有則返回,沒有則新建。有線程安全和線程不安全兩種寫法,區別就是synchronized關鍵字。
[1] 構造方法私有化,防止外界通過構造器創建新的工具類對象
[2] 事先創建好當前類的一個私有靜態對象
[3] 向外暴露一個公共的靜態方法用于返回自身的對象
// 單例模式(懶漢式)
public class Singleton2 {
// [1]私有化構造方法。
private Singleton2() {
}
// [2] 事先創建好當前類的一個私有靜態對象
private static Singleton2 instance = null;
// [3] 向外暴露一個公共的靜態方法用于返回自身的對象
public static Singleton2 getInstance() {
// 被動創建,在真正需要使用時才去創建
if (null == instance) {
instance= new Singleton2();
}
return instance;
}
}
注意:我們從懶漢式單例可以看到,單例實例被延遲加載,即只有在真正使用的時候才會實例化一個對象并交給自己的引用。
優缺點:這種寫法起到了Lazy Loading(懶加載)的效果,但是只能在單線程下使用。如果在多線程下,一個線程進入了if (singleton == null)判斷語句塊,還未來得及往下執行,另一個線程也通過了這個判斷語句,這時便會產生多個實例。所以在多線程環境下不可使用這種方式。
為了解決上面的問題,最簡單的方法是將整個 getInstance() 方法設為同步(synchronized)。
// 單例模式(懶漢式)
public class Singleton2 {
// [1]私有化構造方法。
private Singleton2() {
}
// [2] 事先創建好當前類的一個私有靜態對象
private static Singleton2 instance = null;
// [3] 向外暴露一個公共的靜態方法用于返回自身的對象
public static synchronized Singleton2 getInstance() {
// 被動創建,在真正需要使用時才去創建
if (null == instance) {
instance= new Singleton2();
}
return instance;
}
}
注意:雖然做到了線程安全,并且解決了多實例的問題,但是它并不高效。因為同步操作只需要在第一次調用時才被需要,即第一次創建單例實例對象時,也就是if條件判斷里面的內容。這就引出了雙重檢驗鎖。
雙重檢驗鎖,又叫雙重校驗鎖,綜合了懶漢式和餓漢式兩者的優缺點整合而成。看上面代碼實現中,特點是在synchronized關鍵字內外都加了一層 if 條件判斷,這樣既保證了線程安全,又比直接上鎖提高了執行效率,還節省了內存空間。
// 單例模式(懶漢式)
public class Singleton3 {
private static Singleton3 instance;
private Singleton3 (){}
public static Singleton3 getSingleton() {
if (instance == null) {
synchronized (Singleton3.class) {
if (instance == null) {
instance = new Singleton3();
}
}
}
return instance;
}
}
public class Singleton4 {
private static class SingletonHolder {
private static final Singleton4 INSTANCE = new Singleton4();
}
private Singleton4 (){}
public static final Singleton4 getInstance() {
return SingletonHolder.INSTANCE;
}
}
注意:這種寫法仍然使用JVM本身機制保證了線程安全問題;由于 SingletonHolder 是私有的,除了 getInstance() 之外沒有辦法訪問它,因此它是懶漢式的;同時讀取實例的時候不會進行同步,沒有性能缺陷;也不依賴 JDK 版本。
public enum EasySingleton{
// 定義枚舉常量
INSTANCE;
}
使用:我們可以通過EasySingleton.INSTANCE.工具方法() 的方式來訪問實例,這比調用getInstance()方法簡單多了。創建枚舉默認就是線程安全的,所以不需要擔心double checked locking,而且還能防止反序列化導致重新創建新的對象。
1)網站的計數器,一般也是采用單例模式實現,否則難以同步。
2)應用程序的日志應用,一般都是單例模式實現,只有一個實例去操作才好,否則內容不好追加顯示。
3)多線程的線程池的設計一般也是采用單例模式,因為線程池要方便對池中的線程進行控制
4)Windows的(任務管理器)就是很典型的單例模式,他不能打開倆個
5)windows的(回收站)也是典型的單例應用。在整個系統運行過程中,回收站只維護一個實例。