標籤:內部類 類變數 使用情境 懶載入 無語 bean 單例類 無法 需要
所謂單例,指的就是單一實例,有且僅有一個類執行個體,這個單例不應該由 “人“ 來控制,而應該由 “代碼” 來限制,強制單例。
單例有其專屬的使用情境,一般是對於那些商務邏輯上限定不能多例只能單例的情況,例如:類似於計數器之類的存在,一般都需要使用一個執行個體來進行記錄,若多例計數則會不準確。
其實單例就是那些很明顯的使用場合,沒有之前學習的那些模式所使用的複雜情境,只要你需要使用單例,那你就使用單例,簡單易理解。
所以我認為有關單例模式的重點不在於情境,而在於如何使用。
1、常見的單例模式有兩種建立方式:所謂餓懶漢式與餓漢式
(1)懶漢式
何為懶?顧名思義,就是不做事,這裡也是同義,懶漢式就是不在系統載入時就建立類的單例,而是在第一次使用執行個體的時候再建立。
詳見下方程式碼範例:
public class LanHanDanLi {
//定義一個私人類變數來存放單例,私人的目的是指外部無法直接擷取這個變數,而要使用提供的公用方法來擷取
private static LanHanDanLi lanHanDanLi = null;
//定義一個私人構造器,表示只在類內部使用,也指單例的執行個體只能在單例類內部建立
private LanHanDanLi(){}
//定義一個公用的公開的方法來返回該類的執行個體,由於是懶漢式,需要在第一次使用時產生執行個體,所以為了安全執行緒,使用synchronized關鍵字來確保只會產生單例
public static synchronized LanHanDanLi getInstance(){
if(null == lanHanDanLi){
lanHanDanLi = new LanHanDanLi();
}
return lanHanDanLi;
}
}
(2)餓漢式
又何為餓?餓者,饑不擇食;但凡有食,必急食之。此處同義:在載入類的時候就會建立類的單例,並儲存在類中。
詳見下方程式碼範例:
public class EHanDanLi {
//此處定義類變數執行個體並直接執行個體化,在類載入的時候就完成了執行個體化並儲存在類中
private static EHanDanLi eHanDanLi = new EHanDanLi();
//定義無參構造器,用於單例執行個體
private EHanDanLi(){}
//定義公開方法,返回已建立的單例
public static EHanDanLi getInstance(){
return eHanDanLi;
}
}
2、雙重加鎖機制
何為雙重加鎖機制?
在懶漢式實現單例模式的代碼中,有使用synchronized關鍵字來同步擷取執行個體,保證單例的唯一性,但是上面的代碼在每一次執行時都要進行同步和判斷,無疑會拖慢速度,使用雙重加鎖機制正好可以解決這個問題:
public class SLHanDanli {
private static volatile SLHanDanli dl = null;
private SLHanDanli(){}
public static SLHanDanli getInstance(){
if(dl == null){
synchronized (SLHanDanli.class) {
if(dl == null){
dl = new SLHanDanli();
}
}
}
return dl;
}
}
看了上面的代碼,有沒有感覺很無語,雙重加鎖難道不是需要兩個synchronized進行加鎖的嗎?
......
其實不然,這裡的雙重指的的雙重判斷,而加鎖單指那個synchronized,為什麼要進行雙重判斷,其實很簡單,第一重判斷,如果單例已經存在,那麼就不再需要進行同步操作,而是直接返回這個執行個體,如果沒有建立,才會進入同步塊,同步塊的目的與之前相同,目的是為了防止有兩個調用同時進行時,導致產生多個執行個體,有了同步塊,每次只能有一個線程調用能訪問同步塊內容,當第一個搶到鎖的調用擷取了執行個體之後,這個執行個體就會被建立,之後的所有調用都不會進入同步塊,直接在第一重判斷就返回了單例。至於第二個判斷,個人感覺有點查遺補漏的意味在內(期待高人高見)。
不論如何,使用了雙重加鎖機制後,程式的執行速度有了顯著提升,不必每次都同步加鎖。
其實我最在意的是volatile的使用,volatile關鍵字的含義是:被其所修飾的變數的值不會被本地線程緩衝,所有對該變數的讀寫都是直接操作共用記憶體來實現,從而確保多個線程能正確的處理該變數。該關鍵字可能會屏蔽掉虛擬機器中的一些代碼最佳化,所以其運行效率可能不是很高,所以,一般情況下,並不建議使用雙重加鎖機制,酌情使用才是正理!
3、類級內部類方式
餓漢式會佔用較多的空間,因為其在類載入時就會完成執行個體化,而懶漢式又存在執行速率慢的情況,雙重加鎖機制呢?又有執行效率差的毛病,有沒有一種完美的方式可以規避這些毛病呢?
貌似有的,就是使用類級內部類結合多線程預設同步鎖,同時實現消極式載入和安全執行緒。
public class ClassInnerClassDanli {
public static class DanliHolder{
private static ClassInnerClassDanli dl = new ClassInnerClassDanli();
}
private ClassInnerClassDanli(){}
public static ClassInnerClassDanli getInstance(){
return DanliHolder.dl;
}
}
如上代碼,所謂類級內部類,就是靜態內部類,這種內部類與其外部類之間並沒有從屬關係,載入外部類的時候,並不會同時載入其靜態內部類,只有在發生調用的時候才會進行載入,載入的時候就會建立單例執行個體並返回,有效實現了懶載入(消極式載入),至於同步問題,我們採用和餓漢式同樣的靜態初始化器的方式,藉助JVM來實現安全執行緒。
其實使用靜態初始化器的方式會在類載入時建立類的執行個體,但是我們將執行個體的建立顯式放置在靜態內部類中,它會導致在外部類載入時不進行執行個體建立,這樣就能實現我們的雙重目的:消極式載入和安全執行緒。
4、使用
在Spring中建立的Bean執行個體預設都是單例模式存在的。
出自:http://www.cnblogs.com/V1haoge/p/6510196.html
java單例模式