【創(chuàng)建型模式四】單例模式(Singleton)

1 場(chǎng)景問(wèn)題#

1.1 讀取配置文件的內(nèi)容##

考慮這樣一個(gè)應(yīng)用植康,讀取配置文件的內(nèi)容另凌。

很多應(yīng)用項(xiàng)目,都有與應(yīng)用相關(guān)的配置文件劈榨,這些配置文件多是由項(xiàng)目開發(fā)人員自定義的,在里面定義一些應(yīng)用需要的參數(shù)數(shù)據(jù)晦嵌。當(dāng)然在實(shí)際的項(xiàng)目中同辣,這種配置文件多采用xml格式的。也有采用properties格式的耍铜,畢竟使用Java來(lái)讀取properties格式的配置文件比較簡(jiǎn)單邑闺。

現(xiàn)在要讀取配置文件的內(nèi)容跌前,該如何實(shí)現(xiàn)呢棕兼?

1.2 不用模式的解決方案##

有些朋友會(huì)想,要讀取配置文件的內(nèi)容抵乓,這也不是個(gè)什么困難的事情伴挚,直接讀取文件的內(nèi)容,然后把文件內(nèi)容存放在相應(yīng)的數(shù)據(jù)對(duì)象里面就可以了灾炭。真的這么簡(jiǎn)單嗎茎芋?先實(shí)現(xiàn)看看吧。

為了示例簡(jiǎn)單蜈出,假設(shè)系統(tǒng)是采用的properties格式的配置文件田弥。

  1. 那么直接使用Java來(lái)讀取配置文件,示例代碼如下:
/**
   * 讀取應(yīng)用配置文件
   */  
public class AppConfig {  
      /**
       * 用來(lái)存放配置文件中參數(shù)A的值
       */  
      private String parameterA;  
      /**
       * 用來(lái)存放配置文件中參數(shù)B的值
       */  
      private String parameterB;    

      public String getParameterA() {  
          return parameterA;  
      }  
      public String getParameterB() {  
          return parameterB;  
      }  
      /**
       * 構(gòu)造方法
       */  
      public AppConfig(){  
          //調(diào)用讀取配置文件的方法  
          readConfig();  
      }  
      /**
       * 讀取配置文件铡原,把配置文件中的內(nèi)容讀出來(lái)設(shè)置到屬性上
       */  
      private void readConfig(){  
          Properties p = new Properties();  
          InputStream in = null;  
          try {  
              in = AppConfig.class.getResourceAsStream("AppConfig.properties");  
              p.load(in);  
              //把配置文件中的內(nèi)容讀出來(lái)設(shè)置到屬性上  
              this.parameterA = p.getProperty("paramA");  
              this.parameterB = p.getProperty("paramB");  
          } catch (IOException e) {  
              System.out.println("裝載配置文件出錯(cuò)了偷厦,具體堆棧信息如下:");  
              e.printStackTrace();  
          } finally {  
              try {  
                  in.close();  
              } catch (IOException e) {  
                  e.printStackTrace();  
              }  
          }  
      }  
}

注意:只有訪問(wèn)參數(shù)的方法,沒(méi)有設(shè)置參數(shù)的方法燕刻。

  1. 應(yīng)用的配置文件只泼,名字是AppConfig.properties,放在AppConfig相同的包里面卵洗,簡(jiǎn)單示例如下:
paramA=a  
paramB=b
  1. 寫個(gè)客戶端來(lái)測(cè)試一下请唱,示例代碼如下:
public class Client {  
      public static void main(String[] args) {  
          //創(chuàng)建讀取應(yīng)用配置的對(duì)象  
          AppConfig config = new AppConfig();  

          String paramA = config.getParameterA();  
          String paramB = config.getParameterB();  

          System.out.println("paramA="+paramA+",paramB="+paramB);  
      }  
}  

1.3 有何問(wèn)題##

上面的實(shí)現(xiàn)很簡(jiǎn)單嘛,很容易的就實(shí)現(xiàn)了要求的功能过蹂。仔細(xì)想想十绑,有沒(méi)有什么問(wèn)題呢?

看看客戶端使用這個(gè)類的地方酷勺,是通過(guò)new一個(gè)AppConfig的實(shí)例來(lái)得到一個(gè)操作配置文件內(nèi)容的對(duì)象本橙。如果在系統(tǒng)運(yùn)行中,有很多地方都需要使用配置文件的內(nèi)容鸥印,也就是很多地方都需要?jiǎng)?chuàng)建AppConfig這個(gè)對(duì)象的實(shí)例勋功。

換句話說(shuō)坦报,在系統(tǒng)運(yùn)行期間,系統(tǒng)中會(huì)存在很多個(gè)AppConfig的實(shí)例對(duì)象狂鞋,這有什么問(wèn)題嗎片择?

當(dāng)然有問(wèn)題了,試想一下骚揍,每一個(gè)AppConfig實(shí)例對(duì)象字管,里面都封裝著配置文件的內(nèi)容,系統(tǒng)中有多個(gè)AppConfig實(shí)例對(duì)象信不,也就是說(shuō)系統(tǒng)中會(huì)同時(shí)存在多份配置文件的內(nèi)容嘲叔,這會(huì)嚴(yán)重浪費(fèi)內(nèi)存資源。如果配置文件內(nèi)容較少抽活,問(wèn)題還小一點(diǎn)硫戈,如果配置文件內(nèi)容本來(lái)就多的話,對(duì)于系統(tǒng)資源的浪費(fèi)問(wèn)題就大了下硕。事實(shí)上丁逝,對(duì)于AppConfig這種類,在運(yùn)行期間梭姓,只需要一個(gè)實(shí)例對(duì)象就夠了霜幼。

把上面的描述進(jìn)一步抽象一下,問(wèn)題就出來(lái)了:在一個(gè)系統(tǒng)運(yùn)行期間誉尖,某個(gè)類只需要一個(gè)類實(shí)例就可以了罪既,那么應(yīng)該怎么實(shí)現(xiàn)呢?

2 解決方案#

2.1 單例模式來(lái)解決##

用來(lái)解決上述問(wèn)題的一個(gè)合理的解決方案就是單例模式铡恕。那么什么是單例模式呢琢感?

  1. 單例模式定義

保證一個(gè)類僅有一個(gè)實(shí)例,并提供一個(gè)訪問(wèn)它的全局訪問(wèn)點(diǎn)没咙。

  1. 應(yīng)用單例模式來(lái)解決的思路

仔細(xì)分析上面的問(wèn)題猩谊,現(xiàn)在一個(gè)類能夠被創(chuàng)建多個(gè)實(shí)例,問(wèn)題的根源在于類的構(gòu)造方法是公開的祭刚,也就是可以讓類的外部來(lái)通過(guò)構(gòu)造方法創(chuàng)建多個(gè)實(shí)例牌捷。換句話說(shuō),只要類的構(gòu)造方法能讓類的外部訪問(wèn)涡驮,就沒(méi)有辦法去控制外部來(lái)創(chuàng)建這個(gè)類的實(shí)例個(gè)數(shù)暗甥。

要想控制一個(gè)類只被創(chuàng)建一個(gè)實(shí)例,那么首要的問(wèn)題就是要把創(chuàng)建實(shí)例的權(quán)限收回來(lái)捉捅,讓類自身來(lái)負(fù)責(zé)自己類實(shí)例的創(chuàng)建工作撤防,然后由這個(gè)類來(lái)提供外部可以訪問(wèn)這個(gè)類實(shí)例的方法,這就是單例模式的實(shí)現(xiàn)方式棒口。

2.2 模式結(jié)構(gòu)和說(shuō)明##

單例模式結(jié)構(gòu)如圖所示:

單例模式結(jié)構(gòu)

Singleton:負(fù)責(zé)創(chuàng)建Singleton類自己的唯一實(shí)例寄月,并提供一個(gè)getInstance的方法辜膝,讓外部來(lái)訪問(wèn)這個(gè)類的唯一實(shí)例。

2.3 單例模式示例代碼##

在Java中漾肮,單例模式的實(shí)現(xiàn)又分為兩種厂抖,一種稱為懶漢式,一種稱為餓漢式克懊,其實(shí)就是在具體創(chuàng)建對(duì)象實(shí)例的處理上忱辅,有不同的實(shí)現(xiàn)方式。下面分別來(lái)看這兩種實(shí)現(xiàn)方式的代碼示例谭溉。為何這么寫墙懂,具體的在后面再講述。

  1. 懶漢式實(shí)現(xiàn)扮念,示例代碼如下:
/**
   * 懶漢式單例實(shí)現(xiàn)的示例
   */  
public class Singleton {  
     /**
      * 定義一個(gè)變量來(lái)存儲(chǔ)創(chuàng)建好的類實(shí)例
      */  
     private static Singleton uniqueInstance = null;  
     /**
      * 私有化構(gòu)造方法燕差,好在內(nèi)部控制創(chuàng)建實(shí)例的數(shù)目
      */  
     private Singleton(){  
         //  
     }  
     /**
      * 定義一個(gè)方法來(lái)為客戶端提供類實(shí)例
      * @return 一個(gè)Singleton的實(shí)例
      */  
     public static synchronized Singleton getInstance(){  
         //判斷存儲(chǔ)實(shí)例的變量是否有值  
         if(uniqueInstance == null){  
             //如果沒(méi)有板丽,就創(chuàng)建一個(gè)類實(shí)例旺入,并把值賦值給存儲(chǔ)類實(shí)例的變量  
             uniqueInstance = new Singleton();  
         }  
         //如果有值苞氮,那就直接使用  
         return uniqueInstance;  
     }  
     /**
      * 示意方法谈为,單例可以有自己的操作
      */  
     public void singletonOperation(){  
         //功能處理  
     }  
     /**
      * 示意屬性旅挤,單例可以有自己的屬性
      */  
     private String singletonData;  
     /**
      * 示意方法,讓外部通過(guò)這些方法來(lái)訪問(wèn)屬性的值
      * @return 屬性的值
      */  
     public String getSingletonData(){  
         return singletonData;  
     }  
} 
  1. 餓漢式實(shí)現(xiàn)伞鲫,示例代碼如下:
/**
   * 餓漢式單例實(shí)現(xiàn)的示例
   */  
public class Singleton {  
      /**
       * 定義一個(gè)變量來(lái)存儲(chǔ)創(chuàng)建好的類實(shí)例粘茄,直接在這里創(chuàng)建類實(shí)例,只會(huì)創(chuàng)建一次
       */  
      private static Singleton uniqueInstance = new Singleton();  
      /**
       * 私有化構(gòu)造方法秕脓,好在內(nèi)部控制創(chuàng)建實(shí)例的數(shù)目
       */  
      private Singleton(){  
          //  
      }  
      /**
       * 定義一個(gè)方法來(lái)為客戶端提供類實(shí)例
       * @return 一個(gè)Singleton的實(shí)例
       */  
      public static Singleton getInstance(){  
          //直接使用已經(jīng)創(chuàng)建好的實(shí)例  
          return uniqueInstance;  
      }  

      /**
       * 示意方法柒瓣,單例可以有自己的操作
       */  
      public void singletonOperation(){  
          //功能處理  
      }  
      /**
       * 示意屬性,單例可以有自己的屬性
       */  
      private String singletonData;  
      /**
       * 示意方法吠架,讓外部通過(guò)這些方法來(lái)訪問(wèn)屬性的值
       * @return 屬性的值
       */  
      public String getSingletonData(){  
          return singletonData;  
      }  
}  

2.4 使用單例模式重寫示例##

要使用單例模式來(lái)重寫示例芙贫,由于單例模式有兩種實(shí)現(xiàn)方式,這里選一種來(lái)實(shí)現(xiàn)就好了傍药,就選擇餓漢式的實(shí)現(xiàn)方式來(lái)重寫示例吧磺平。采用餓漢式的實(shí)現(xiàn)方式來(lái)重寫實(shí)例的示例代碼如下:

/**
 * 讀取應(yīng)用配置文件,單例實(shí)現(xiàn)
 */  
public class AppConfig {  
    /**
     * 定義一個(gè)變量來(lái)存儲(chǔ)創(chuàng)建好的類實(shí)例拐辽,直接在這里創(chuàng)建類實(shí)例拣挪,只會(huì)創(chuàng)建一次
     */  
    private static AppConfig instance = new AppConfig();  
    /**
     * 定義一個(gè)方法來(lái)為客戶端提供AppConfig類的實(shí)例
     * @return 一個(gè)AppConfig的實(shí)例
     */  
    public static AppConfig getInstance(){  
        return instance;  
    }  

    /**
     * 用來(lái)存放配置文件中參數(shù)A的值
     */  
    private String parameterA;  
    /**
     * 用來(lái)存放配置文件中參數(shù)B的值
     */  
    private String parameterB;  
    public String getParameterA() {  
        return parameterA;  
    }  
    public String getParameterB() {  
        return parameterB;  
    }  
    /**
     * 私有化構(gòu)造方法
     */  
    private AppConfig(){  
        //調(diào)用讀取配置文件的方法  
        readConfig();  
    }  
    /**
     * 讀取配置文件,把配置文件中的內(nèi)容讀出來(lái)設(shè)置到屬性上
     */  
    private void readConfig(){  
        Properties p = new Properties();  
        InputStream in = null;  
        try {  
            in = AppConfig.class.getResourceAsStream("AppConfig.properties");  
            p.load(in);  
            //把配置文件中的內(nèi)容讀出來(lái)設(shè)置到屬性上  
            this.parameterA = p.getProperty("paramA");  
            this.parameterB = p.getProperty("paramB");  
        } catch (IOException e) {  
            System.out.println("裝載配置文件出錯(cuò)了俱诸,具體堆棧信息如下:");  
            e.printStackTrace();  
        } finally {  
            try {  
                in.close();  
            } catch (IOException e) {  
                e.printStackTrace();  
            }  
        }  
    }    
}

當(dāng)然菠劝,測(cè)試的客戶端也需要相應(yīng)的變化,示例代碼如下:

public class Client {  
    public static void main(String[] args) {  
        //創(chuàng)建讀取應(yīng)用配置的對(duì)象  
        AppConfig config = AppConfig.getInstance();  

        String paramA = config.getParameterA();  
        String paramB = config.getParameterB();  

        System.out.println("paramA="+paramA+",paramB="+paramB);  
    }
}

3 模式講解#

3.1 認(rèn)識(shí)單例模式##

  1. 單例模式的功能

單例模式的功能是用來(lái)保證這個(gè)類在運(yùn)行期間只會(huì)被創(chuàng)建一個(gè)類實(shí)例睁搭,另外單例模式還提供了一個(gè)全局唯一訪問(wèn)這個(gè)類實(shí)例的訪問(wèn)點(diǎn)赶诊,就是那個(gè)getInstance的方法笼平。不管采用懶漢式還是餓漢式的實(shí)現(xiàn)方式,這個(gè)全局訪問(wèn)點(diǎn)是一樣的舔痪。

對(duì)于單例模式而言出吹,不管采用何種實(shí)現(xiàn)方式,它都是只關(guān)心類實(shí)例的創(chuàng)建問(wèn)題辙喂,并不關(guān)心具體的業(yè)務(wù)功能捶牢。

  1. 單例模式的范圍

也就是在多大范圍內(nèi)是單例呢?

觀察上面的實(shí)現(xiàn)可以知道巍耗,目前Java里面實(shí)現(xiàn)的單例是一個(gè)ClassLoader及其子ClassLoader的范圍秋麸。因?yàn)橐粋€(gè)ClassLoader在裝載餓漢式實(shí)現(xiàn)的單例類的時(shí)候就會(huì)創(chuàng)建一個(gè)類的實(shí)例

這就意味著如果一個(gè)虛擬機(jī)里面有很多個(gè)ClassLoader炬太,而且這些ClassLoader都裝載某個(gè)類的話灸蟆,就算這個(gè)類是單例,它也會(huì)產(chǎn)生很多個(gè)實(shí)例亲族。當(dāng)然炒考,如果一個(gè)機(jī)器上有多個(gè)虛擬機(jī),那么每個(gè)虛擬機(jī)里面都應(yīng)該至少有一個(gè)這個(gè)類的實(shí)例霎迫,也就是說(shuō)整個(gè)機(jī)器上就有很多個(gè)實(shí)例斋枢,更不會(huì)是單例了。

另外請(qǐng)注意一點(diǎn)知给,這里討論的單例模式并不適用于集群環(huán)境瓤帚,對(duì)于集群環(huán)境下的單例這里不去討論,那不屬于這里的內(nèi)容范圍涩赢。

  1. 單例模式的命名

一般建議單例模式的方法命名為:getInstance()戈次,這個(gè)方法的返回類型肯定是單例類的類型了。getInstance方法可以有參數(shù)筒扒,這些參數(shù)可能是創(chuàng)建類實(shí)例所需要的參數(shù)怯邪,當(dāng)然,大多數(shù)情況下是不需要的花墩。

單例模式的名稱:?jiǎn)卫渭误w等等观游,翻譯的不同搂捧,都是指的同一個(gè)模式。

3.2 懶漢式和餓漢式實(shí)現(xiàn)##

前面提到了單例模式有兩種典型的解決方案懂缕,一種叫懶漢式允跑,一種叫餓漢式,這兩種方式究竟是如何實(shí)現(xiàn)的,下面分別來(lái)看看聋丝。為了看得更清晰一點(diǎn)索烹,只是實(shí)現(xiàn)基本的單例控制部分,不再提供示例的屬性和方法了弱睦;而且暫時(shí)也不去考慮線程安全的問(wèn)題百姓,這個(gè)問(wèn)題在后面會(huì)重點(diǎn)分析

第一種方案 懶漢式

  1. 私有化構(gòu)造方法:

要想在運(yùn)行期間控制某一個(gè)類的實(shí)例只有一個(gè)况木,那首先的任務(wù)就是要控制創(chuàng)建實(shí)例的地方垒拢,也就是不能隨隨便便就可以創(chuàng)建類實(shí)例,否則就無(wú)法控制創(chuàng)建的實(shí)例個(gè)數(shù)了』鹁現(xiàn)在是讓使用類的地方來(lái)創(chuàng)建類實(shí)例求类,也就是在類外部來(lái)創(chuàng)建類實(shí)例。那么怎樣才能讓類的外部不能創(chuàng)建一個(gè)類的實(shí)例呢屹耐?很簡(jiǎn)單尸疆,私有化構(gòu)造方法就可以了!

private Singleton() {  
}  
  1. 提供獲取實(shí)例的方法

構(gòu)造方法被私有化了惶岭,外部使用這個(gè)類的地方不干了寿弱,外部創(chuàng)建不了類實(shí)例就沒(méi)有辦法調(diào)用這個(gè)對(duì)象的方法,就實(shí)現(xiàn)不了功能處理按灶,這可不行症革。經(jīng)過(guò)思考,單例模式?jīng)Q定讓這個(gè)類提供一個(gè)方法來(lái)返回類的實(shí)例兆衅,好讓外面使用地沮。示例代碼如下:

public Singleton getInstance() {  
}
  1. 把獲取實(shí)例的方法變成靜態(tài)的

又有新的問(wèn)題了,獲取對(duì)象實(shí)例的這個(gè)方法是個(gè)實(shí)例方法羡亩,也就是說(shuō)客戶端要想調(diào)用這個(gè)方法,需要先得到類實(shí)例危融,然后才可以調(diào)用畏铆,可是這個(gè)方法就是為了得到類實(shí)例,這樣一來(lái)不就形成一個(gè)死循環(huán)了嗎吉殃?這不就是典型的“先有雞還是先有蛋的問(wèn)題”嘛辞居。

解決方法也很簡(jiǎn)單,在方法上加上static蛋勺,這樣就可以直接通過(guò)類來(lái)調(diào)用這個(gè)方法瓦灶,而不需要先得到類實(shí)例了,示例代碼如下:

public static Singleton getInstance() {  
}  
  1. 定義存儲(chǔ)實(shí)例的屬性

方法定義好了抱完,那么方法內(nèi)部如何實(shí)現(xiàn)呢贼陶?如果直接創(chuàng)建實(shí)例并返回,這樣行不行呢?示例代碼如下:

public static Singleton getInstance(){  
      return new Singleton();  
}  

當(dāng)然不行了碉怔,如果每次客戶端訪問(wèn)都這樣直接new一個(gè)實(shí)例烘贴,那肯定會(huì)有多個(gè)實(shí)例,根本實(shí)現(xiàn)不了單例的功能撮胧。

怎么辦呢桨踪?單例模式想到了一個(gè)辦法,那就是用一個(gè)屬性來(lái)記錄自己創(chuàng)建好的類實(shí)例芹啥,當(dāng)?shù)谝淮蝿?chuàng)建過(guò)后锻离,就把這個(gè)實(shí)例保存下來(lái),以后就可以復(fù)用這個(gè)實(shí)例墓怀,而不是重復(fù)創(chuàng)建對(duì)象實(shí)例了纳账。示例代碼如下:

private Singleton instance = null; 
  1. 把這個(gè)屬性也定義成靜態(tài)的

這個(gè)屬性變量應(yīng)該在什么地方用呢?肯定是第一次創(chuàng)建類實(shí)例的地方捺疼,也就是在前面那個(gè)返回對(duì)象實(shí)例的靜態(tài)方法里面使用疏虫。

由于要在一個(gè)靜態(tài)方法里面使用,所以這個(gè)屬性被迫成為一個(gè)類變量啤呼,要強(qiáng)制加上static卧秘,也就是說(shuō),這里并沒(méi)有使用static的特性官扣。示例代碼如下:

private static Singleton instance = null;
  1. 實(shí)現(xiàn)控制實(shí)例的創(chuàng)建

現(xiàn)在應(yīng)該到getInstance方法里面實(shí)現(xiàn)控制實(shí)例創(chuàng)建了翅敌,控制的方式很簡(jiǎn)單,只要先判斷一下惕蹄,是否已經(jīng)創(chuàng)建過(guò)實(shí)例了蚯涮。如何判斷?那就看存放實(shí)例的屬性是否有值卖陵,如果有值遭顶,說(shuō)明已經(jīng)創(chuàng)建過(guò)了,如果沒(méi)有值泪蔫,那就是應(yīng)該創(chuàng)建一個(gè)棒旗,示例代碼如下:

public static Singleton getInstance() {  
      //先判斷instance是否有值  
      if (instance == null) {  
          //如果沒(méi)有值,說(shuō)明還沒(méi)有創(chuàng)建過(guò)實(shí)例撩荣,那就創(chuàng)建一個(gè)  
          //并把這個(gè)實(shí)例設(shè)置給instance  
          instance = new Singleton ();  
      }  
      //如果有值铣揉,或者是創(chuàng)建了值,那就直接使用  
      return instance;  
}
  1. 完整的實(shí)現(xiàn)

至此餐曹,成功解決了:在運(yùn)行期間逛拱,控制某個(gè)類只被創(chuàng)建一個(gè)實(shí)例的要求。完整的代碼如下台猴,為了大家好理解朽合,用注釋標(biāo)示了代碼的先后順序俱两,示例代碼如下:

public class Singleton {  
      //4:定義一個(gè)變量來(lái)存儲(chǔ)創(chuàng)建好的類實(shí)例  
      //5:因?yàn)檫@個(gè)變量要在靜態(tài)方法中使用,所以需要加上static修飾  
      private static Singleton instance = null;  
      //1:私有化構(gòu)造方法旁舰,好在內(nèi)部控制創(chuàng)建實(shí)例的數(shù)目  
      private Singleton(){      
      }  
      //2:定義一個(gè)方法來(lái)為客戶端提供類實(shí)例  
      //3:這個(gè)方法需要定義成類方法锋华,也就是要加static  
      public static Singleton getInstance(){  
          //6:判斷存儲(chǔ)實(shí)例的變量是否有值  
          if(instance == null){  
              //6.1:如果沒(méi)有,就創(chuàng)建一個(gè)類實(shí)例箭窜,并把值賦值給存儲(chǔ)類實(shí)例的變量  
              instance = new Singleton();  
          }  
          //6.2:如果有值毯焕,那就直接使用  
          return instance;  
      }  
}

第二種方案 餓漢式

這種方案跟第一種方案相比,前面的私有化構(gòu)造方法磺樱,提供靜態(tài)的getInstance方法來(lái)返回實(shí)例等步驟都一樣纳猫。差別在如何實(shí)現(xiàn)getInstance方法,在這個(gè)地方竹捉,單例模式還想到了另外一種方法來(lái)實(shí)現(xiàn)getInstance方法芜辕。

不就是要控制只創(chuàng)造一個(gè)實(shí)例嗎?那么有沒(méi)有什么現(xiàn)成的解決辦法呢块差?很快侵续,單例模式回憶起了Java中static的特性:

static變量在類裝載的時(shí)候進(jìn)行初始化。

多個(gè)實(shí)例的static變量會(huì)共享同一塊內(nèi)存區(qū)域憨闰。

這就意味著状蜗,在Java中,static變量只會(huì)被初始化一次鹉动,就是在類裝載的時(shí)候轧坎,而且多個(gè)實(shí)例都會(huì)共享這個(gè)內(nèi)存空間,這不就是單例模式要實(shí)現(xiàn)的功能嗎泽示?真是得來(lái)全不費(fèi)功夫啊缸血。根據(jù)這些知識(shí),寫出了第二種解決方案的代碼械筛,示例代碼如下:

public class Singleton {  
    //4:定義一個(gè)靜態(tài)變量來(lái)存儲(chǔ)創(chuàng)建好的類實(shí)例  
    //直接在這里創(chuàng)建類實(shí)例捎泻,只會(huì)創(chuàng)建一次  
    private static Singleton instance = new Singleton();  
    //1:私有化構(gòu)造方法,好在內(nèi)部控制創(chuàng)建實(shí)例的數(shù)目  
    private Singleton(){          
    }  
    //2:定義一個(gè)方法來(lái)為客戶端提供類實(shí)例  
    //3:這個(gè)方法需要定義成類方法变姨,也就是要加static  
    //這個(gè)方法里面就不需要控制代碼了  
    public static Singleton getInstance(){  
        //5:直接使用已經(jīng)創(chuàng)建好的實(shí)例  
        return instance;  
    }  
}  

不管是采用哪一種方式族扰,在運(yùn)行期間,都只會(huì)生成一個(gè)實(shí)例定欧,而訪問(wèn)這些類的一個(gè)全局訪問(wèn)點(diǎn),就是那個(gè)靜態(tài)的getInstance方法怒竿。

單例模式的調(diào)用順序示意圖

先看懶漢式的調(diào)用順序砍鸠,如圖所示:

懶漢式的調(diào)用順序

餓漢式的調(diào)用順序,如圖所示:

餓漢式的調(diào)用順序

3.3 延遲加載的思想##

單例模式的懶漢式實(shí)現(xiàn)方式體現(xiàn)了延遲加載的思想耕驰,什么是延遲加載呢爷辱?

通俗點(diǎn)說(shuō),就是一開始不要加載資源或者數(shù)據(jù),一直等饭弓,等到馬上就要使用這個(gè)資源或者數(shù)據(jù)了双饥,躲不過(guò)去了才加載,所以也稱Lazy Load弟断,不是懶惰啊咏花,是“延遲加載”,這在實(shí)際開發(fā)中是一種很常見(jiàn)的思想阀趴,盡可能的節(jié)約資源昏翰。

體現(xiàn)在什么地方呢?看如下代碼:

懶漢式代碼片段

3.4 緩存的思想##

單例模式的懶漢式實(shí)現(xiàn)還體現(xiàn)了緩存的思想刘急,緩存也是實(shí)際開發(fā)中非常常見(jiàn)的功能棚菊。

簡(jiǎn)單講就是,如果某些資源或者數(shù)據(jù)會(huì)被頻繁的使用叔汁,而這些資源或數(shù)據(jù)存儲(chǔ)在系統(tǒng)外部统求,比如數(shù)據(jù)庫(kù)、硬盤文件等据块,那么每次操作這些數(shù)據(jù)的時(shí)候都從數(shù)據(jù)庫(kù)或者硬盤上去獲取码邻,速度會(huì)很慢,會(huì)造成性能問(wèn)題瑰钮。

一個(gè)簡(jiǎn)單的解決方法就是:把這些數(shù)據(jù)緩存到內(nèi)存里面冒滩,每次操作的時(shí)候,先到內(nèi)存里面找浪谴,看有沒(méi)有這些數(shù)據(jù)开睡,如果有,那么就直接使用苟耻,如果沒(méi)有那么就獲取它篇恒,并設(shè)置到緩存中,下一次訪問(wèn)的時(shí)候就可以直接從內(nèi)存中獲取了凶杖。從而節(jié)省大量的時(shí)間胁艰,當(dāng)然,緩存是一種典型的空間換時(shí)間的方案智蝠。

緩存在單例模式的實(shí)現(xiàn)中怎么體現(xiàn)的呢腾么?

單例模式中緩存思想

3.5 Java中緩存的基本實(shí)現(xiàn)##

引申一下,看看在Java開發(fā)中的緩存的基本實(shí)現(xiàn)杈湾,在Java中最常見(jiàn)的一種實(shí)現(xiàn)緩存的方式就是使用Map解虱,基本的步驟是:

  1. 先到緩存里面查找,看看是否存在需要使用的數(shù)據(jù)

  2. 如果沒(méi)有找到漆撞,那么就創(chuàng)建一個(gè)滿足要求的數(shù)據(jù)殴泰,然后把這個(gè)數(shù)據(jù)設(shè)置回到緩存中于宙,以備下次使用

  3. 如果找到了相應(yīng)的數(shù)據(jù),或者是創(chuàng)建了相應(yīng)的數(shù)據(jù)悍汛,那就直接使用這個(gè)數(shù)據(jù)捞魁。

還是看看示例吧,示例代碼如下:

/**
 * Java中緩存的基本實(shí)現(xiàn)示例
 */  
public class JavaCache {  
   /**
    * 緩存數(shù)據(jù)的容器离咐,定義成Map是方便訪問(wèn)谱俭,直接根據(jù)Key就可以獲取Value了
    * key選用String是為了簡(jiǎn)單,方便演示
    */  
   private Map<String,Object> map = new HashMap<String,Object>();  
   /**
    * 從緩存中獲取值
    * @param key 設(shè)置時(shí)候的key值
    * @return key對(duì)應(yīng)的Value值
    */  
   public Object getValue(String key){  
       //先從緩存里面取值  
       Object obj = map.get(key);  
       //判斷緩存里面是否有值  
       if(obj == null){  
           //如果沒(méi)有健霹,那么就去獲取相應(yīng)的數(shù)據(jù)旺上,比如讀取數(shù)據(jù)庫(kù)或者文件  
           //這里只是演示,所以直接寫個(gè)假的值  
           obj = key+",value";  
           //把獲取的值設(shè)置回到緩存里面  
           map.put(key, obj);  
       }  
       //如果有值了糖埋,就直接返回使用  
       return obj;  
   }  
}  

這里只是緩存的基本實(shí)現(xiàn)宣吱,還有很多功能都沒(méi)有考慮,比如緩存的清除瞳别,緩存的同步等等征候。當(dāng)然,Java的緩存還有很多實(shí)現(xiàn)方式祟敛,也是非常復(fù)雜的疤坝,現(xiàn)在有很多專業(yè)的緩存框架,更多緩存的知識(shí)馆铁,這里就不再去討論了跑揉。

3.6 利用緩存來(lái)實(shí)現(xiàn)單例模式##

其實(shí)應(yīng)用Java緩存的知識(shí),也可以變相實(shí)現(xiàn)Singleton模式埠巨,算是一個(gè)模擬實(shí)現(xiàn)吧历谍。每次都先從緩存中取值,只要?jiǎng)?chuàng)建一次對(duì)象實(shí)例過(guò)后辣垒,就設(shè)置了緩存的值望侈,那么下次就不用再創(chuàng)建了。

雖然不是很標(biāo)準(zhǔn)的做法勋桶,但是同樣可以實(shí)現(xiàn)單例模式的功能脱衙,為了簡(jiǎn)單,先不去考慮多線程的問(wèn)題例驹,示例代碼如下:

/**
 * 使用緩存來(lái)模擬實(shí)現(xiàn)單例
 */  
public class Singleton {  
   /**
    * 定義一個(gè)缺省的key值捐韩,用來(lái)標(biāo)識(shí)在緩存中的存放
    */  
   private final static String DEFAULT_KEY = "One";  
   /**
    * 緩存實(shí)例的容器
    */  
   private static Map<String,Singleton> map = new HashMap<String,Singleton>();  
   /**
    * 私有化構(gòu)造方法
    */  
   private Singleton(){  
       //  
   }  
   public static Singleton getInstance(){  
       //先從緩存中獲取  
       Singleton instance = (Singleton)map.get(DEFAULT_KEY);  
       //如果沒(méi)有,就新建一個(gè)鹃锈,然后設(shè)置回緩存中  
       if(instance==null){  
           instance = new Singleton();  
           map.put(DEFAULT_KEY, instance);  
       }  
       //如果有就直接使用  
       return instance;  
   }  
}  

3.7 單例模式的優(yōu)缺點(diǎn)##

  1. 時(shí)間和空間

懶漢式是典型的時(shí)間換空間奥帘,也就是每次獲取實(shí)例都會(huì)進(jìn)行判斷,看是否需要?jiǎng)?chuàng)建實(shí)例仪召,費(fèi)判斷的時(shí)間寨蹋,當(dāng)然,如果一直沒(méi)有人使用的話扔茅,那就不會(huì)創(chuàng)建實(shí)例已旧,節(jié)約內(nèi)存空間。

餓漢式是典型的空間換時(shí)間召娜,當(dāng)類裝載的時(shí)候就會(huì)創(chuàng)建類實(shí)例运褪,不管你用不用,先創(chuàng)建出來(lái)玖瘸,然后每次調(diào)用的時(shí)候秸讹,就不需要再判斷了,節(jié)省了運(yùn)行時(shí)間雅倒。

  1. 線程安全

(1)從線程安全性上講璃诀,不加同步的懶漢式是線程不安全的,比如說(shuō):有兩個(gè)線程蔑匣,一個(gè)是線程A劣欢,一個(gè)是線程B,它們同時(shí)調(diào)用getInstance方法裁良,那就可能導(dǎo)致并發(fā)問(wèn)題凿将。如下示例:

線程不安全分析1

程序繼續(xù)運(yùn)行,兩個(gè)線程都向前走了一步价脾,如下:

線程不安全分析2

可能有些朋友會(huì)覺(jué)得文字描述還是不夠直觀牧抵,再來(lái)畫個(gè)圖說(shuō)明一下,如圖所示:

線程不安全分析

通過(guò)上圖的分解描述侨把,明顯可以看出犀变,當(dāng)A、B線程并發(fā)的情況下座硕,會(huì)創(chuàng)建出兩個(gè)實(shí)例來(lái)弛作,也就是單例的控制在并發(fā)情況下失效了。

(2)餓漢式是線程安全的华匾,因?yàn)樘摂M機(jī)保證了只會(huì)裝載一次映琳,在裝載類的時(shí)候是不會(huì)發(fā)生并發(fā)的。

(3)如何實(shí)現(xiàn)懶漢式的線程安全呢蜘拉?當(dāng)然懶漢式也是可以實(shí)現(xiàn)線程安全的萨西,只要加上synchronized即可,如下:

public static synchronized Singleton getInstance(){} 

但是這樣一來(lái)旭旭,會(huì)降低整個(gè)訪問(wèn)的速度谎脯,而且每次都要判斷,也確實(shí)是稍微慢點(diǎn)持寄。那么有沒(méi)有更好的方式來(lái)實(shí)現(xiàn)呢源梭?

(4)雙重檢查加鎖娱俺,可以使用“雙重檢查加鎖”的方式來(lái)實(shí)現(xiàn),就可以既實(shí)現(xiàn)線程安全废麻,又能夠使性能不受到大的影響荠卷。那么什么是“雙重檢查加鎖”機(jī)制呢?

所謂雙重檢查加鎖機(jī)制烛愧,指的是:并不是每次進(jìn)入getInstance方法都需要同步油宜,而是先不同步,進(jìn)入方法過(guò)后怜姿,先檢查實(shí)例是否存在慎冤,如果不存在才進(jìn)入下面的同步塊,這是第一重檢查沧卢。進(jìn)入同步塊過(guò)后蚁堤,再次檢查實(shí)例是否存在,如果不存在搏恤,就在同步的情況下創(chuàng)建一個(gè)實(shí)例违寿,這是第二重檢查。這樣一來(lái)熟空,就只需要同步一次了藤巢,從而減少了多次在同步情況下進(jìn)行判斷所浪費(fèi)的時(shí)間。

雙重檢查加鎖機(jī)制的實(shí)現(xiàn)會(huì)使用一個(gè)關(guān)鍵字volatile息罗,它的意思是:

被volatile修飾的變量的值掂咒,將不會(huì)被本地線程緩存,所有對(duì)該變量的讀寫都是直接操作共享內(nèi)存迈喉,從而確保多個(gè)線程能正確的處理該變量绍刮。

注意:在Java1.4及以前版本中,很多JVM對(duì)于volatile關(guān)鍵字的實(shí)現(xiàn)有問(wèn)題挨摸,會(huì)導(dǎo)致雙重檢查加鎖的失敗孩革,因此雙重檢查加鎖的機(jī)制只能用在Java5及以上的版本。

看看代碼可能會(huì)更清楚些得运,示例代碼如下:

public class Singleton {  
      /**
       * 對(duì)保存實(shí)例的變量添加volatile的修飾
       */  
      private volatile static Singleton instance = null;  
      private Singleton(){      
      }

      public static  Singleton getInstance(){  
          //先檢查實(shí)例是否存在膝蜈,如果不存在才進(jìn)入下面的同步塊  
          if(instance == null){  
              //同步塊,線程安全的創(chuàng)建實(shí)例  
              synchronized(Singleton.class){  
                  //再次檢查實(shí)例是否存在熔掺,如果不存在才真的創(chuàng)建實(shí)例  
                  if(instance == null){  
                      instance = new Singleton();  
                  }  
              }  
          }  
          return instance;  
      }  
}

這種實(shí)現(xiàn)方式既可使實(shí)現(xiàn)線程安全的創(chuàng)建實(shí)例饱搏,又不會(huì)對(duì)性能造成太大的影響,它只是在第一次創(chuàng)建實(shí)例的時(shí)候同步置逻,以后就不需要同步了推沸,從而加快運(yùn)行速度

提示:

由于volatile關(guān)鍵字可能會(huì)屏蔽掉虛擬機(jī)中一些必要的代碼優(yōu)化,所以運(yùn)行效率并不是很高鬓催,因此一般建議肺素,沒(méi)有特別的需要,不要使用深浮。也就是說(shuō)压怠,雖然可以使用雙重加鎖機(jī)制來(lái)實(shí)現(xiàn)線程安全的單例,但并不建議大量采用飞苇,根據(jù)情況來(lái)選用吧。

3.8 在Java中一種更好的單例實(shí)現(xiàn)方式##

根據(jù)上面的分析蜗顽,常見(jiàn)的兩種單例實(shí)現(xiàn)方式都存在小小的缺陷布卡,那么有沒(méi)有一種方案,既能夠?qū)崿F(xiàn)延遲加載雇盖,又能夠?qū)崿F(xiàn)線程安全呢忿等?

還真有高人想到這樣的解決方案了,這個(gè)解決方案被稱為L(zhǎng)azy initialization holder class模式崔挖,這個(gè)模式綜合使用了Java的類級(jí)內(nèi)部類和多線程缺省同步鎖的知識(shí)贸街,很巧妙的同時(shí)實(shí)現(xiàn)了延遲加載和線程安全。

  1. 先來(lái)看點(diǎn)相應(yīng)的基礎(chǔ)知識(shí)

什么是類級(jí)內(nèi)部類狸相?簡(jiǎn)單點(diǎn)說(shuō)薛匪,類級(jí)內(nèi)部類指的是:有static修飾的成員式內(nèi)部類。如果沒(méi)有static修飾的成員式內(nèi)部類被稱為對(duì)象級(jí)內(nèi)部類脓鹃。

類級(jí)內(nèi)部類相當(dāng)于其外部類的static成分逸尖,它的對(duì)象與外部類對(duì)象間不存在依賴關(guān)系,因此可直接創(chuàng)建瘸右。而對(duì)象級(jí)內(nèi)部類的實(shí)例娇跟,是綁定在外部對(duì)象實(shí)例中的。

類級(jí)內(nèi)部類中太颤,可以定義靜態(tài)的方法苞俘,在靜態(tài)方法中只能夠引用外部類中的靜態(tài)成員方法或者成員變量。

類級(jí)內(nèi)部類相當(dāng)于其外部類的成員龄章,只有在第一次被使用的時(shí)候才會(huì)被裝載吃谣。

再來(lái)看看多線程缺省同步鎖的知識(shí)。

大家都知道瓦堵,在多線程開發(fā)中基协,為了解決并發(fā)問(wèn)題,主要是通過(guò)使用synchronized來(lái)加互斥鎖進(jìn)行同步控制菇用。

但是在某些情況中澜驮,JVM已經(jīng)隱含地為您執(zhí)行了同步,這些情況下就不用自己再來(lái)進(jìn)行同步控制了惋鸥。這些情況包括:

  1. 由靜態(tài)初始化器(在靜態(tài)字段上或 static{} 塊中的初始化器)初始化數(shù)據(jù)時(shí)

  2. 訪問(wèn) final 字段時(shí)

  3. 在創(chuàng)建線程之前創(chuàng)建對(duì)象時(shí)

  4. 線程可以看見(jiàn)它將要處理的對(duì)象時(shí)

  1. 接下來(lái)看看這種解決方案的思路

要想很簡(jiǎn)單的實(shí)現(xiàn)線程安全杂穷,可以采用靜態(tài)初始化器的方式悍缠,它可以由JVM來(lái)保證線程安全性。比如前面的“餓漢式”實(shí)現(xiàn)方式耐量,但是這樣一來(lái)飞蚓,不是會(huì)浪費(fèi)一定的空間嗎?因?yàn)檫@種實(shí)現(xiàn)方式廊蜒,會(huì)在類裝載的時(shí)候就初始化對(duì)象趴拧,不管你需不需要。

如果現(xiàn)在有一種方法能夠讓類裝載的時(shí)候不去初始化對(duì)象山叮,那不就解決問(wèn)題了著榴?一種可行的方式就是采用類級(jí)內(nèi)部類,在這個(gè)類級(jí)內(nèi)部類里面去創(chuàng)建對(duì)象實(shí)例屁倔,這樣一來(lái)脑又,只要不使用到這個(gè)類級(jí)內(nèi)部類,那就不會(huì)創(chuàng)建對(duì)象實(shí)例锐借。從而同時(shí)實(shí)現(xiàn)延遲加載和線程安全问麸。

看看代碼示例可能會(huì)更清晰,示例代碼如下:

public class Singleton {  
      /**
       * 類級(jí)的內(nèi)部類钞翔,也就是靜態(tài)的成員式內(nèi)部類严卖,該內(nèi)部類的實(shí)例與外部類的實(shí)例
       * 沒(méi)有綁定關(guān)系,而且只有被調(diào)用到才會(huì)裝載嗅战,從而實(shí)現(xiàn)了延遲加載
       */  
      private static class SingletonHolder {  
          /**
           * 靜態(tài)初始化器妄田,由JVM來(lái)保證線程安全
           */  
          private static Singleton instance = new Singleton();  
      }  
      /**
       * 私有化構(gòu)造方法
       */  
      private Singleton() {  
      }  
      public static  Singleton getInstance() {  
          return SingletonHolder.instance;  
      }  
}

仔細(xì)想想,是不是很巧妙呢驮捍!

當(dāng)getInstance方法第一次被調(diào)用的時(shí)候疟呐,它第一次讀取SingletonHolder.instance,導(dǎo)致SingletonHolder類得到初始化东且;而這個(gè)類在裝載并被初始化的時(shí)候启具,會(huì)初始化它的靜態(tài)域,從而創(chuàng)建Singleton的實(shí)例珊泳,由于是靜態(tài)的域鲁冯,因此只會(huì)被虛擬機(jī)在裝載類的時(shí)候初始化一次,并由虛擬機(jī)來(lái)保證它的線程安全性色查。

這個(gè)模式的優(yōu)勢(shì)在于薯演,getInstance方法并沒(méi)有被同步,并且只是執(zhí)行一個(gè)域的訪問(wèn)秧了,因此延遲初始化并沒(méi)有增加任何訪問(wèn)成本跨扮。

3.9 單例和枚舉##

按照《高效Java 第二版》中的說(shuō)法:單元素的枚舉類型已經(jīng)成為實(shí)現(xiàn)Singleton的最佳方法

為了理解這個(gè)觀點(diǎn),先來(lái)了解一點(diǎn)相關(guān)的枚舉知識(shí)衡创,這里只是強(qiáng)化和總結(jié)一下枚舉的一些重要觀點(diǎn)帝嗡,更多基本的枚舉的使用,請(qǐng)參看Java編程入門資料:

  1. Java的枚舉類型實(shí)質(zhì)上是功能齊全的類璃氢,因此可以有自己的屬性和方法哟玷;

  2. Java枚舉類型的基本思想:通過(guò)公有的靜態(tài)final域?yàn)槊總€(gè)枚舉常量導(dǎo)出實(shí)例的類

  3. 從某個(gè)角度講一也,枚舉是單例的泛型化巢寡,本質(zhì)上是單元素的枚舉

用枚舉來(lái)實(shí)現(xiàn)單例非常簡(jiǎn)單塘秦,只需要編寫一個(gè)包含單個(gè)元素的枚舉類型即可讼渊,示例代碼如下:

/**
 * 使用枚舉來(lái)實(shí)現(xiàn)單例模式的示例
 */  
public enum Singleton {  
    /**
     * 定義一個(gè)枚舉的元素,它就代表了Singleton的一個(gè)實(shí)例
     */  
    uniqueInstance;  

    /**
     * 示意方法,單例可以有自己的操作
     */  
    public void singletonOperation(){  
        //功能處理  
    }  
}  

使用枚舉來(lái)實(shí)現(xiàn)單實(shí)例控制尊剔,會(huì)更加簡(jiǎn)潔,而且無(wú)償?shù)奶峁┝诵蛄谢臋C(jī)制菱皆,并由JVM從根本上提供保障须误,絕對(duì)防止多次實(shí)例化,是更簡(jiǎn)潔仇轻、高效京痢、安全的實(shí)現(xiàn)單例的方式。

3.10 思考單例模式##

  1. 單例模式的本質(zhì)

單例模式的本質(zhì):控制實(shí)例數(shù)目篷店。

單例模式是為了控制在運(yùn)行期間祭椰,某些類的實(shí)例數(shù)目只能有一個(gè)∑I拢可能有人就會(huì)想了方淤,那么我能不能控制實(shí)例數(shù)目為2個(gè),3個(gè)蹄殃,或者是任意多個(gè)呢携茂?目的都是一樣的,節(jié)省資源啊诅岩,有些時(shí)候單個(gè)實(shí)例不能滿足實(shí)際的需要讳苦,會(huì)忙不過(guò)來(lái),根據(jù)測(cè)算吩谦,3個(gè)實(shí)例剛剛好鸳谜,也就是說(shuō),現(xiàn)在要控制實(shí)例數(shù)目為3個(gè)式廷,怎么辦呢咐扭?

其實(shí)思路很簡(jiǎn)單,就是利用上面通過(guò)Map來(lái)緩存實(shí)現(xiàn)單例的示例,進(jìn)行變形草描,一個(gè)Map可以緩存任意多個(gè)實(shí)例览绿,新的問(wèn)題就是,Map中有多個(gè)實(shí)例穗慕,但是客戶端調(diào)用的時(shí)候饿敲,到底返回那一個(gè)實(shí)例呢?逛绵,也就是實(shí)例的調(diào)度問(wèn)題怀各,我們只是想要來(lái)展示設(shè)計(jì)模式,對(duì)于這個(gè)調(diào)度算法就不去深究了术浪,做個(gè)最簡(jiǎn)單的瓢对,循環(huán)返回就好了,示例代碼如下:

/**
   * 簡(jiǎn)單演示如何擴(kuò)展單例模式胰苏,控制實(shí)例數(shù)目為3個(gè)
   */  
public class OneExtend {  
      /**
       * 定義一個(gè)缺省的key值的前綴
       */  
      private final static String DEFAULT_PREKEY = "Cache";  
      /**
       * 緩存實(shí)例的容器
       */  
      private static Map<String,OneExtend> map = new HashMap<String,OneExtend>();  
      /**
       * 用來(lái)記錄當(dāng)前正在使用第幾個(gè)實(shí)例硕蛹,到了控制的最大數(shù)目,就返回從1開始
       */  
      private static int num = 1;  
      /**
       * 定義控制實(shí)例的最大數(shù)目
       */  
      private final static int NUM_MAX = 3;  
      private OneExtend(){}  
      public static OneExtend getInstance(){  
          String key = DEFAULT_PREKEY+num;  
          //緩存的體現(xiàn)硕并,通過(guò)控制緩存的數(shù)據(jù)多少來(lái)控制實(shí)例數(shù)目  
          OneExtend oneExtend = map.get(key);  
          if(oneExtend==null){  
              oneExtend = new OneExtend();  
              map.put(key, oneExtend);  
          }  
          //把當(dāng)前實(shí)例的序號(hào)加1  
          num++;  
          if(num > NUM_MAX){  
              //如果實(shí)例的序號(hào)已經(jīng)達(dá)到最大數(shù)目了法焰,那就重復(fù)從1開始獲取  
              num = 1;  
          }  
          return oneExtend;        
      }  

      public static void main(String[] args) {  
          //測(cè)試是否能滿足功能要求  
          OneExtend t1 = getInstance ();  
          OneExtend t2 = getInstance ();  
          OneExtend t3 = getInstance ();  
          OneExtend t4 = getInstance ();  
          OneExtend t5 = getInstance ();  
          OneExtend t6 = getInstance ();  

          System.out.println("t1=="+t1);  
          System.out.println("t2=="+t2);  
          System.out.println("t3=="+t3);  
          System.out.println("t4=="+t4);  
          System.out.println("t5=="+t5);  
          System.out.println("t6=="+t6);  
      }  
} 

測(cè)試一下,看看結(jié)果倔毙,如下:

t1==cn.javass.dp.singleton.example9.OneExtend@6b97fd
t2==cn.javass.dp.singleton.example9.OneExtend@1c78e57
t3==cn.javass.dp.singleton.example9.OneExtend@5224ee
t4==cn.javass.dp.singleton.example9.OneExtend@6b97fd
t5==cn.javass.dp.singleton.example9.OneExtend@1c78e57
t6==cn.javass.dp.singleton.example9.OneExtend@5224ee
  1. 何時(shí)選用單例模式

建議在如下情況中埃仪,選用單例模式:

當(dāng)需要控制一個(gè)類的實(shí)例只能有一個(gè),而且客戶只能從一個(gè)全局訪問(wèn)點(diǎn)訪問(wèn)它時(shí)陕赃,可以選用單例模式卵蛉,這些功能恰好是單例模式要解決的問(wèn)題。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末么库,一起剝皮案震驚了整個(gè)濱河市傻丝,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌廊散,老刑警劉巖桑滩,帶你破解...
    沈念sama閱讀 218,682評(píng)論 6 507
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場(chǎng)離奇詭異允睹,居然都是意外死亡运准,警方通過(guò)查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,277評(píng)論 3 395
  • 文/潘曉璐 我一進(jìn)店門缭受,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái)胁澳,“玉大人,你說(shuō)我怎么就攤上這事米者【禄” “怎么了宇智?”我有些...
    開封第一講書人閱讀 165,083評(píng)論 0 355
  • 文/不壞的土叔 我叫張陵,是天一觀的道長(zhǎng)胰丁。 經(jīng)常有香客問(wèn)我随橘,道長(zhǎng),這世上最難降的妖魔是什么锦庸? 我笑而不...
    開封第一講書人閱讀 58,763評(píng)論 1 295
  • 正文 為了忘掉前任机蔗,我火速辦了婚禮,結(jié)果婚禮上甘萧,老公的妹妹穿的比我還像新娘萝嘁。我一直安慰自己,他們只是感情好扬卷,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,785評(píng)論 6 392
  • 文/花漫 我一把揭開白布牙言。 她就那樣靜靜地躺著,像睡著了一般怪得。 火紅的嫁衣襯著肌膚如雪咱枉。 梳的紋絲不亂的頭發(fā)上泉手,一...
    開封第一講書人閱讀 51,624評(píng)論 1 305
  • 那天邓了,我揣著相機(jī)與錄音,去河邊找鬼褪尝。 笑死因谎,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的颜懊。 我是一名探鬼主播财岔,決...
    沈念sama閱讀 40,358評(píng)論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼河爹!你這毒婦竟也來(lái)了匠璧?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 39,261評(píng)論 0 276
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤咸这,失蹤者是張志新(化名)和其女友劉穎夷恍,沒(méi)想到半個(gè)月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體媳维,經(jīng)...
    沈念sama閱讀 45,722評(píng)論 1 315
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡酿雪,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,900評(píng)論 3 336
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了侄刽。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片指黎。...
    茶點(diǎn)故事閱讀 40,030評(píng)論 1 350
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖州丹,靈堂內(nèi)的尸體忽然破棺而出醋安,到底是詐尸還是另有隱情杂彭,我是刑警寧澤,帶...
    沈念sama閱讀 35,737評(píng)論 5 346
  • 正文 年R本政府宣布吓揪,位于F島的核電站亲怠,受9級(jí)特大地震影響,放射性物質(zhì)發(fā)生泄漏柠辞。R本人自食惡果不足惜团秽,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,360評(píng)論 3 330
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望钾腺。 院中可真熱鬧徙垫,春花似錦、人聲如沸放棒。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,941評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)间螟。三九已至吴旋,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間厢破,已是汗流浹背荣瑟。 一陣腳步聲響...
    開封第一講書人閱讀 33,057評(píng)論 1 270
  • 我被黑心中介騙來(lái)泰國(guó)打工, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留摩泪,地道東北人笆焰。 一個(gè)月前我還...
    沈念sama閱讀 48,237評(píng)論 3 371
  • 正文 我出身青樓,卻偏偏與公主長(zhǎng)得像见坑,于是被迫代替她去往敵國(guó)和親嚷掠。 傳聞我的和親對(duì)象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,976評(píng)論 2 355

推薦閱讀更多精彩內(nèi)容

  • 【學(xué)習(xí)難度:★☆☆☆☆荞驴,使用頻率:★★★★☆】直接出處:?jiǎn)卫J绞崂砗蛯W(xué)習(xí):https://github.com/...
    BruceOuyang閱讀 675評(píng)論 1 2
  • 動(dòng)機(jī) 有些情況下不皆,一個(gè)類只能有一個(gè)實(shí)例是很重要的。比如說(shuō)熊楼,在操作系統(tǒng)中只能有一個(gè)窗口管理器的(文件系統(tǒng)或打印機(jī)程序...
    holysu閱讀 1,261評(píng)論 0 0
  • 欣兒_12de閱讀 243評(píng)論 0 0
  • 來(lái)簡(jiǎn)書已經(jīng)有半個(gè)月了鲫骗,日更第十天犬耻。 首先就是感謝吧,感謝從何而來(lái)呢挎峦? 因?yàn)槭且粋€(gè)新人香追,剛來(lái)簡(jiǎn)書有很多東西都不清楚不...
    大寫R1閱讀 163評(píng)論 0 3
  • 先說(shuō)餃子皮。餃子皮最好是自己搟的坦胶,菜市場(chǎng)一般有機(jī)器做的餃子皮透典,跟手搟的不可同日而語(yǔ)晴楔。 說(shuō)到餃子皮,就要說(shuō)一下和面峭咒。...
    尚太極閱讀 757評(píng)論 0 2