1. 程式人生 > >單例模式:為什麼要雙重檢測

單例模式:為什麼要雙重檢測

http://blog.sina.com.cn/s/blog_6b6468720100kpif.html

3.3  延遲載入的思想

        單例模式的懶漢式實現方式體現了延遲載入的思想,什麼是延遲載入呢?
        通俗點說,就是一開始不要載入資源或者資料,一直等,等到馬上就要使用這個資源或者資料了,躲不過去了才載入,所以也稱Lazy Load,不是懶惰啊,是“延遲載入”,這在實際開發中是一種很常見的思想,儘可能的節約資源。
        體現在什麼地方呢?看如下程式碼:

研磨設計模式之 <wbr>單例模式-3 
 

3.4  快取的思想

        單例模式的懶漢式實現還體現了快取的思想,快取也是實際開發中非常常見的功能。
        簡單講就是,如果某些資源或者資料會被頻繁的使用,而這些資源或資料儲存在系統外部,比如資料庫、硬碟檔案等,那麼每次操作這些資料的時候都從資料庫或者硬碟上去獲取,速度會很慢,會造成效能問題。
        一個簡單的解決方法就是:把這些資料快取到記憶體裡面,每次操作的時候,先到記憶體裡面找,看有沒有這些資料,如果有,那麼就直接使用,如果沒有那麼就獲取它,並設定到快取中,下一次訪問的時候就可以直接從記憶體中獲取了。從而節省大量的時間,當然,快取是一種典型的空間換時間的方案。
        快取在單例模式的實現中怎麼體現的呢?

研磨設計模式之 <wbr>單例模式-3

 3.5  Java中快取的基本實現

        引申一下,看看在Java開發中的快取的基本實現,在Java中最常見的一種實現快取的方式就是使用Map,基本的步驟是:

  • 先到快取裡面查詢,看看是否存在需要使用的資料
  • 如果沒有找到,那麼就建立一個滿足要求的資料,然後把這個資料設定回到快取中,以備下次使用
  • 如果找到了相應的資料,或者是建立了相應的資料,那就直接使用這個資料。

 還是看看示例吧,示例程式碼如下:

01
04 public class JavaCache {
05
09 private Map<String,Object> map = new HashMap<String,Object>();
10
15 public Object getValue(String key){
16 //先從快取裡面取值
17 Object obj = map.get(key);
18 //判斷快取裡面是否有值
19 if(obj == null){
20 //如果沒有,那麼就去獲取相應的資料,比如讀取資料庫或者檔案
21 //這裡只是演示,所以直接寫個假的值
22 obj = key+",value";
23 //把獲取的值設定回到快取裡面
24 map.put(key, obj);
25 }
26 //如果有值了,就直接返回使用
27 return obj;
28 }
29 }

        這裡只是快取的基本實現,還有很多功能都沒有考慮,比如快取的清除,快取的同步等等。當然,Java的快取還有很多實現方式,也是非常複雜的,現在有很多專業的快取框架,更多快取的知識,這裡就不再去討論了。

3.6  利用快取來實現單例模式

        其實應用Java快取的知識,也可以變相實現Singleton模式,算是一個模擬實現吧。每次都先從快取中取值,只要建立一次物件例項過後,就設定了快取的值,那麼下次就不用再建立了。
        雖然不是很標準的做法,但是同樣可以實現單例模式的功能,為了簡單,先不去考慮多執行緒的問題,示例程式碼如下:

01
04 public class Singleton {
05
08 private final static String DEFAULT_KEY = "One";
09
12 private static Map<String,Singleton> map = 
13 new HashMap<String,Singleton>();
14
17 private Singleton(){
18 //
19 }
20 public static Singleton getInstance(){
21 //先從快取中獲取
22 Singleton instance = (Singleton)map.get(DEFAULT_KEY);
23 //如果沒有,就新建一個,然後設定回快取中
24 if(instance==null){
25 instance = new Singleton();
26 map.put(DEFAULT_KEY, instance);
27 }
28 //如果有就直接使用
29 return instance;
30 }
31 }

        是不是也能實現單例所要求的功能呢?其實實現模式的方式有很多種,並不是只有模式的參考實現所實現的方式,上面這種也能實現單例所要求的功能,只不過實現比較麻煩,不是太好而已,但在後面擴充套件單例模式的時候會有用。
        另外,模式是經驗的積累,模式的參考實現並不一定是最優的,對於單例模式,後面會給大家一些更好的實現方式。


3.7  單例模式的優缺點

1:時間和空間
        比較上面兩種寫法:懶漢式是典型的時間換空間,也就是每次獲取例項都會進行判斷,看是否需要建立例項,費判斷的時間,當然,如果一直沒有人使用的話,那就不會建立例項,節約記憶體空間。
        餓漢式是典型的空間換時間,當類裝載的時候就會建立類例項,不管你用不用,先創建出來,然後每次呼叫的時候,就不需要再判斷了,節省了執行時間。

2:執行緒安全
(1)從執行緒安全性上講,不加同步的懶漢式是執行緒不安全的,比如說:有兩個執行緒,一個是執行緒A,一個是執行緒B,它們同時呼叫getInstance方法,那就可能導致併發問題。如下示例:

研磨設計模式之 <wbr>單例模式-3

 程式繼續執行,兩個執行緒都向前走了一步,如下:


研磨設計模式之 <wbr>單例模式-3 
 

可能有些朋友會覺得文字描述還是不夠直觀,再來畫個圖說明一下,如圖4所示:


研磨設計模式之 <wbr>單例模式-3 
                                                       圖4  懶漢式單例的執行緒問題示意圖

        通過圖4的分解描述,明顯可以看出,當A、B執行緒併發的情況下,會創建出兩個例項來,也就是單例的控制在併發情況下失效了。


(2)餓漢式是執行緒安全的,因為虛擬機器保證了只會裝載一次,在裝載類的時候是不會發生併發的。

(3)如何實現懶漢式的執行緒安全呢?
        當然懶漢式也是可以實現執行緒安全的,只要加上synchronized即可,如下:

1 public static synchronized Singleton getInstance(){}

         但是這樣一來,會降低整個訪問的速度,而且每次都要判斷,也確實是稍微慢點。那麼有沒有更好的方式來實現呢?

(4)雙重檢查加鎖
        可以使用“雙重檢查加鎖”的方式來實現,就可以既實現執行緒安全,又能夠使效能不受到大的影響。那麼什麼是“雙重檢查加鎖”機制呢?
        所謂雙重檢查加鎖機制,指的是:並不是每次進入getInstance方法都需要同步,而是先不同步,進入方法過後,先檢查例項是否存在,如果不存在才進入下面的同步塊,這是第一重檢查。進入同步塊過後,再次檢查例項是否存在,如果不存在,就在同步的情況下建立一個例項,這是第二重檢查。這樣一來,就只需要同步一次了,從而減少了多次在同步情況下進行判斷所浪費的時間。
        雙重檢查加鎖機制的實現會使用一個關鍵字volatile,它的意思是:被volatile修飾的變數的值,將不會被本地執行緒快取,所有對該變數的讀寫都是直接操作共享記憶體,從而確保多個執行緒能正確的處理該變數。
        注意:在Java1.4及以前版本中,很多JVM對於volatile關鍵字的實現有問題,會導致雙重檢查加鎖的失敗,因此雙重檢查加鎖的機制只能用在Java5及以上的版本。
        看看程式碼可能會更清楚些,示例程式碼如下:

01 public class Singleton {
02
05 private volatile static Singleton instance = null;
06 private Singleton(){    
07 }
08 public static  Singleton getInstance(){
09 //先檢查例項是否存在,如果不存在才進入下面的同步塊
10 if(instance == null){
11 //同步塊,執行緒安全的建立例項
12 synchronized(Singleton.class){
13 //再次檢查例項是否存在,如果不存在才真的建立例項
14 if(instance == null){
15 instance = new Singleton();
16 }
17 }
18 }
19 return instance;
20 }
21 }
 <wbr> <wbr></wbr></wbr>

         這種實現方式既可使實現執行緒安全的建立例項,又不會對效能造成太大的影響,它只是在第一次建立例項的時候同步,以後就不需要同步了,從而加快執行速度。
         提示:由於volatile關鍵字可能會遮蔽掉虛擬機器中一些必要的程式碼優化,所以執行效率並不是很高,因此一般建議,沒有特別的需要,不要使用。也就是說,雖然可以使用雙重加鎖機制來實現執行緒安全的單例,但並不建議大量採用,根據情況來選用吧。


相關推薦

模式為什麼雙重檢測

http://blog.sina.com.cn/s/blog_6b6468720100kpif.html 3.3  延遲載入的思想         單例模式的懶漢式實現方式體現了延遲載入的思想,什麼是延遲載入呢?         通俗點說,就是一開始不要載入資源或者

java之模式餓漢式、懶漢式、雙重校驗鎖、列舉、靜態內部類

一、餓漢式: /** * 餓漢式: * 不存在多執行緒同步問題,當類被載入時,初始化並分配記憶體空間; * 當類被解除安裝時,才釋放所佔記憶體,因此在某些特定條件下會耗費記憶體。 * * @author: Rodge * @time: 2018年10月4日 下午4:35:12 * @

模式建立獨一無二的物件

單例模式(Singleton Pattern): 用來建立獨一無二的,只能有一個例項的物件的入場券。 作用:有些物件我們只需要一個,比如:執行緒池、快取、對話方塊、處理偏好設定、登錄檔等物件,這些物件只能有一個例項,如果製造出多個例項,就會導致很多問題產生,例如:程式行為異常、資源使用過量、或者

模式層層剖析尋找最高效安全的

問題來源  什麼是單例?它的運用場景是什麼?   單例模式是指保證在系統中只存在某類唯一物件。運用場景隨處可見,例如工具類、Spring容器預設new物件等。   單例模式有幾種實現方式?   餓漢式、懶漢式、雙重檢查鎖式、內部類式、列舉式。   推薦使用方式?   餓漢

模式中的雙重檢查加鎖

本文是在學習單例模式時遇到的問題 在多執行緒中,如何防止單例模式被多次例項,當然是要加鎖啦。但是加了鎖就意味著執行緒雖然安全,但效率肯定會變低,這是,就出現了雙重檢查加鎖。但看到這段程式碼,我又有疑問了? public class Singleton { private vo

模式兩把鎖版本

Singleton Pattern Ensure a class has only one instance,and provide a global point of access to it . 單例模式的幾個要點: 私有的構造器。禁止外部使用new關鍵字得到例

【C++】模式餓漢模式和懶漢模式

餓漢模式:提前建立一個靜態的類物件,把所有能夠建立物件的模組全部私有化,從外部需要建立類物件時只能返回事先建立好的唯一物件。就像一個特別飢餓的人,提前準備好食物,只要餓了,就可以立刻食用。 /*惡漢模式--單例模式*/ #include<iostream> using namespa

模式餓漢和懶漢

接下來就說下單例模式了,這個在實際應用還是比較常用的! 首先,單例分為懶漢式和餓漢式: 餓漢式:類載入的時候,建立物件。 因此類載入速度慢, 執行緒相對安全 懶漢式:類載入的時候,不會建立物件,呼叫時

模式餓漢式和懶漢式

餓漢式:載入類的時候,就建立了物件 /** * 餓漢式:載入類的時候,就建立了物件 */ public class Ehanshi { // 建立物件 private static Ehanshi ehanshi = new Ehanshi(); // 無參

深入理解模式靜態內部類原理

這樣的 加載 hand 優點 傳遞 多個 喚醒 ref 一個   本文主要介紹java的單例模式,以及詳細剖析靜態內部類之所以能夠實現單例的原理。OK,廢話不多說,進入正文。    首先我們要先了解下單例的四大原則:    1.構造私有。    2.以靜態方法或者枚舉返回實

C# 基礎(十四)C#模式首先介紹 執行緒、多執行緒、加鎖 模式。然後介紹模式的執行緒同步多執行緒有序訪問共享記憶體。

一、簡介 本篇文章將介紹如何使用單例模式,也就是類的例項化,在整個專案的生命週期內,只例項化一次。在單例模式中,往往可以看到如SourceCode.cs:這樣的結構的。 SourceCode.cs: public class Singleton { private static

JAVA模式就是把構造方法弄成私有的

一.問題引入   偶然想想到的如果把Java的構造方法弄成private,那裡面的成員屬性是不是隻有通過static來訪問呢;如果構造方法是private的話,那麼有什麼好處呢;如果構造方法是private的話,會不更好的封裝該內呢?我主要是應用在使用普通類模擬列舉型別裡,後來發現這就是傳說中的單例模式。建

Head First 設計模式(C++實現)模式Singleton

單例模式:確保一個類只有一個例項,並提供一個全域性訪問點 1 .經典單例模式實現   我們都很清楚一個簡單的單例模式該怎樣去實現:建構函式宣告為private或protect防止被外部函式例項化,內部儲存一個private static的類指標儲存唯一的例項,例項的動

Java 模式中使用雙重檢查(Double-Check)

在 Effecitve Java 一書的第 48 條中提到了雙重檢查模式,並指出這種模式在 Java 中通常並不適用。該模式的結構如下所示: public Resource getResource() {     if (resource == null)

android之模式懶漢式和餓漢式的區別

單例模式:懶漢式和餓漢式    餓漢式:執行緒安全:構造方法私有化:推薦使用          public class Singleton{            private static Si

模式中 的 雙重檢查鎖 概念與用法

它的 lock acc env syn 可見 cost ola check public class Singleton { //私有的 靜態的 本類屬性 private volatile static Singleton _instance;

模式二-懶漢模式(Lazy)

原文: Gerrard_Feng 二:2-懶漢模式(Lazy)    思想:相比於餓漢模式,懶漢模式實際中的應用更多,因為在系統中

模式雙重檢測

單例模式是設計模式中比較常見簡單的一種,典型雙重檢測寫法如下: public class SingletonClass { private volatile static SingletonClass instance = null; public static SingletonCl

設計模式(一):單例模式 JVM類載入機制 JDK原始碼學習筆記——Enum列舉使用及原理 Java併發(七):雙重檢驗鎖定DCL Java併發(二)Java記憶體模型 Java併發(二)Java記憶體模型 Java併發(七):雙重檢驗鎖定DCL JDK原始碼學習筆記——Enum列舉使用及原理

單例模式是一種常用的軟體設計模式,其定義是單例物件的類只能允許一個例項存在。 單例模式一般體現在類宣告中,單例的類負責建立自己的物件,同時確保只有單個物件被建立。這個類提供了一種訪問其唯一的物件的方式,可以直接訪問,不需要例項化該類的物件。 適用場合: 需要頻繁的進行建立和銷燬的物件; 建立物

正確的寫法雙重檢測同步鎖實現

隨便百度一下, 雙重檢測,單例的 都是這樣 class Singleton{ private static Sing