資料借鑑:http://cantellow.iteye.com/blog/838473java
簡單介紹:
單例模式是一種常常用到的軟件設計模式。在它的核心結構中只包含一個被稱爲單例的特殊類。經過單例模式能夠保證系統中應用該模式的類只有一個實例。即一個類只有一個實例
定義:
一個類有且只有一個實例,而且自行實例化向整個系統提供。
特色:
一、單例類只能有一個實例。
二、單例類必須本身建立本身的惟一實例。
三、單例類必須給全部其餘對象提供這一實例。
實現方式:
通常來講,咱們有如下幾個必要的操做:
一、私有化構造方法;
二、final類(定義爲不可繼承,這點書上沒有提到,暫時還在研究)
三、將類的實例對象定義爲一個私有的屬性(不限制爲成員變量)
四、經過getInstance()方法獲取實例,若私有的實例屬性對象引用不爲空則返回,不然實例化該屬性並返回
這裏先介紹四種實現方式
一、餓漢模式
二、懶漢模式
三、雙重驗證
四、靜態內部類模式
先看第一種方式,餓漢模式顧名思義,火燒眉毛的想要吃(初始化實例),在類中定義一個私有靜態本類的實例化對象,在類加載的過程就進行此對象的實例化,以後的對此類實例的調用都是調用此實例。
代碼以下:
public class Singleton2 {
private static Singleton2 singleton2= new Singleton2();
private Singleton2(){}
public static Singleton2 getInstance() {
return singleton2;
}
public static String getStr() {
return "Create String type Object";
}
}
餓漢模式是較爲簡單的實現方式,一樣也是較爲經常使用的方式。但他有着必定的缺陷:雖然在單例模式中大多都只調用getInstance()方法,但不排除有其餘的方式致使類加載,好比若是類中getStr()這種與類的實例無關的方法若是被調用,就會觸發類加載,從而對靜態成員進行初始化,可是此類有可能並不須要實例化,這樣在某種程度上會形成必定的資源浪費。也就沒法達到lazy loading的效果。
這就引入了懶漢模式
懶漢模式:只有在第一此調用類的實例對象時纔會初始化類的實例,從而實現延遲加載
代碼以下
public class Singleton3 {
private static Singleton3 singleton2 = null;
private Singleton3(){
Tools.println("類實例化");
}
public static synchronized Singleton3 getInstance(){
if(singleton2 == null)
singleton2 = new Singleton3();
return singleton2;
}
public static void CreateString(){
Tools.print("Create String in Singleton3");
}
}
懶漢模式經過getInstance()方法建立實例,這樣只有在使用到實例的時候纔會初始化實例對象,實現了延遲加載,可是這種模式會出現線程同步問題:一個線程調用了getInstace()方法,判斷爲空後進行實例的建立,此時又有了一個線程調用了getInstance()方法,但此刻第一個線程尚未完成實例化的操做,故此線程也會實例化一個對象。因此咱們須要爲getInstance()方法加上同步關鍵字synchronized 。
那麼問題來了,咱們使用延遲加載就是爲了提高系統性能,而引入了同步關鍵字則會大大影響多線程狀況下的性能,因此此方式也有這很大的缺陷。
下面就引入了雙重檢測方式
雙重檢測方式:經過雙重檢測的方式完成延遲加載
代碼以下:
class Singleton1 {
private Singleton1() {
}
public static Singleton1 instance = null;
public static Singleton1 getInstance() {
if (instance == null) {
synchronized (Singleton1.class) {
if (instance == null) {
instance = new Singleton1();
}
}
}
return instance;
}
}
能夠看到,首先判斷實例對象是否爲空,若是判斷經過再進行同步操做。
接下來引入一種已經較爲完善而且使用較多的一種實現方式:靜態內部類實現
代碼以下:
public class Singleton4 {
private Singleton4(){}
static class SingletonHolder {
private static Singleton4 singleton = new Singleton4();
}
public static Singleton4 getInstance(){
return SingletonHolder.singleton;
}
}
此方式利用了:靜態內部類並不會在外部類類加載的時候也進行類加載,而是在它自身第一次被使用時進行類加載,而且jvm的類加載過程是對線程很是友好的,因此咱們不須要擔憂同步問題。
public class Singleton4 {
private Singleton4(){}
static class SingletonHolder {
private static Singleton4 singleton = new Singleton4();
}
public static Singleton4 getInstance(){
return SingletonHolder.singleton;
}
}
上述方法都是基本上實現了單例模式,可是依然有兩個問題須要注意:
1:若是單例由不一樣的類裝載器注入,那邊有可能存在有多個單例類的實例。假如不是遠端存取,假如一些servlet容器對每一個servlet使用不一樣的類裝載器,他們就會有個字的單例類的實例。
2:若是單例類實現了java.io.Serializable接口,那麼此類的實例就能夠被序列化和復原,若是序列化一個對象,而後復原多個此對象,就會出現多個單例類的實例。
第一個問題的解決方法:
private static Class getClass(String classname)
throws ClassNotFoundException {
ClassLoader classLoader = Thread.currentThread().getContextClassLoader();
if(classLoader == null)
classLoader = Singleton.class.getClassLoader();
return (classLoader.loadClass(classname));
}
}
第二個問題的解決方法:
public class Singleton implements java.io.Serializable {
public static Singleton INSTANCE = new Singleton();
protected Singleton() {
}
private Object readResolve() {
return INSTANCE;
}
}
這兩種方法是我從其餘的博客上看來的,如今還在瞭解中。。。
應用場景:
1. Windows的Task Manager(任務管理器)就是很典型的單例模式(這個很熟悉吧),想一想看,是否是呢,你能打開兩個windows task manager嗎? 不信你本身試試看哦~
2. windows的Recycle Bin(回收站)也是典型的單例應用。在整個系統運行過程當中,回收站一直維護着僅有的一個實例。
3. 網站的計數器,通常也是採用單例模式實現,不然難以同步。
4. 應用程序的日誌應用,通常都何用單例模式實現,這通常是因爲共享的日誌文件一直處於打開狀態,由於只能有一個實例去操做,不然內容很差追加。
5. Web應用的配置對象的讀取,通常也應用單例模式,這個是因爲配置文件是共享的資源。
6. 數據庫鏈接池的設計通常也是採用單例模式,由於數據庫鏈接是一種數據庫資源。數據庫軟件系統中使用數據庫鏈接池,主要是節省打開或者關閉數據庫鏈接所引發的效率損耗,這種效率上的損耗仍是很是昂貴的,由於何用單例模式來維護,就能夠大大下降這種損耗。
7. 多線程的線程池的設計通常也是採用單例模式,這是因爲線程池要方便對池中的線程進行控制。
8. 操做系統的文件系統,也是大的單例模式實現的具體例子,一個操做系統只能有一個文件系統。
9. HttpApplication 也是單位例的典型應用。熟悉ASP.Net(IIS)的整個請求生命週期的人應該知道HttpApplication也是單例模式,全部的HttpModule都共享一個HttpApplication實例.
總結以上,不難看出:
單例模式應用的場景通常發如今如下條件下:
(1)資源共享的狀況下,避免因爲資源操做時致使的性能或損耗等。如上述中的日誌文件,應用配置。
(2)控制資源的狀況下,方便資源之間的互相通訊。如線程池等。