Java單例模式詳解

九天高遠發表於2013-08-22

 一、基本概念

單例模式有以下特點:
  1、單例類只能有一個例項。
  2、單例類必須自己自己建立自己的唯一例項。
  3、單例類必須給所有其他物件提供這一例項。
  單例模式確保某個類只有一個例項,而且自行例項化並向整個系統提供這個例項。在計算機系統中,執行緒池、快取、日誌物件、對話方塊、印表機、顯示卡的驅動程式物件常被設計成單例。怎樣才能保證一個類只有一個例項並且這個例項易於被訪問呢?一個全域性變數使得一個物件可以被訪問,但它不能防止你例項化多個物件,一個更好的方法是讓類自身負責儲存他的唯一例項。這個類可以保證沒有其他例項可以被建立,並且它可以提供一個訪問該例項的方法,這就是Singleton模式。

    在下面的情況下可以使用Singleton模式。
  • 當類只能有一個例項而且客戶可以從一個眾所周知的訪問點訪問它時。
  • 當這個唯一例項應該是透過子類化可擴充套件的,並且客戶應該無需更改程式碼就能使用一個擴充套件的例項時。

 二、經典實現

1、上述程式碼為單例模式的懶漢式,在第一次呼叫的時候例項化。

package com.yunhe.singleton;

//懶漢式單例類,在第一次呼叫的時候例項化 
public class Singleton {
//注意這裡沒有final
private static Singleton instance=null;
//私有的構造方法
private Singleton(){    
}
//靜態工廠方法
public static Singleton getInstance(){
    if(instance==null){
        return new Singleton();
    }
    return instance;
}

}

       Singleton透過將構造方法限定為private避免了類在外部被例項化,在同一個JVM虛擬機器範圍內,Singleton的唯一例項只能透過getInstance()方法訪問。(事實上,透過Java反射機制是能夠例項化這種類(即構造方法為private型別)的,那基本上會使所有的Java單例實現失效。此問題在此處不做討論,姑且掩耳盜鈴地認為反射機制不存在。)

但是以上實現沒有考慮執行緒安全問題。所謂執行緒安全是指:如果你的程式碼所在的程式中有多個執行緒在同時執行,而這些執行緒可能會同時執行這段程式碼。如果每次執行結果和單執行緒執行的結果是一樣的,而且其他的變數的值也和預期的是一樣的,就是執行緒安全的。或者說:一個類或者程式所提供的介面對於執行緒來說是原子操作或者多個執行緒之間的切換不會導致該介面的執行結果存在二義性,也就是說我們不用考慮同步的問題。顯然以上實現並不滿足執行緒安全的要求,在併發環境下很可能出現多個Singleton例項。

所以,如果在多執行緒下面操縱這一單例,需要自getInstance的靜態方法上面新增sychronized。

2、下面是單例模式的餓漢式,在類初始化時,已經自行例項化。

package com.yunhe.singleton;

//
餓漢式單例,在類初始化時,已經自行例項化 public class Singleton2 { //已經自行例項化 private static final Singleton2 instance=new Singleton2(); //私有的預設建構函式 private Singleton2(){ } //靜態工廠方法 public static Singleton2 getInstance(){ return instance; } }

3、下面是單例模式的登記式

注:static就是在類被第一次載入的時候執行,以後就不再執行。

package com.yunhe.singleton;
import java.util.HashMap;
import java.util.Map;

//登記式單例類
//類似Spring裡面的方法,將類名註冊,下次從裡面直接獲取。
public class Singleton3 {
    private static Map<String,Singleton3> map = new HashMap<String,Singleton3>();
    static{
        Singleton3 instance = new Singleton3();
        map.put(instance.getClass().getName(), instance);
    }
    //保護的預設建構函式
    protected Singleton3(){}
    //靜態工廠方法,返回此類惟一的例項
    public static Singleton3 getInstance(String name) {
        if(name == null) {
            name = Singleton3.class.getName();
            System.out.println("name == null"+"--->name="+name);
        }
        if(map.get(name) == null) {
            try {
                map.put(name, (Singleton3) Class.forName(name).newInstance());
            } catch (InstantiationException e) {
                e.printStackTrace();
            } catch (IllegalAccessException e) {
                e.printStackTrace();
            } catch (ClassNotFoundException e) {
                e.printStackTrace();
            }
        }
        return map.get(name);
    }
    //一個示意性的商業方法
    public String about() {    
        return "Hello, I am RegSingleton.";    
    }    
    public static void main(String[] args) {
        Singleton3 instance3 = Singleton3.getInstance(null);
        System.out.println(instance3.about());
    }
}

 三、單例模式相關示例

示例一:

package com.yunhe.singleton;
public class TestPerson {
    //注意之類沒有final
    private static TestPerson tp1=null;
    private String name;
    public String getName() {
        return name;
    }
    public void setName(String name) {
        this.name = name;
    } 

    //私有無參構造方法
    private TestPerson(){}    

    //這個類必須自動向整個系統提供這個例項物件
    public static TestPerson getIntance(){
        if(tp1==null){
            tp1=new TestPerson();
        }
        return tp1;
    }
    public void getInfo(){
        System.out.println("output message: "+name);
    }
}

客戶端呼叫程式碼:

package com.yunhe.singleton;
public class TestMain {
    public static void main(String [] args){
        TestPerson s=TestPerson.getIntance();
        s.setName("劉德華");
        System.out.println(s.getName());
        TestPerson s1=TestPerson.getIntance();
        s1.setName("張信哲");
        System.out.println(s1.getName());
        s.getInfo();
        s1.getInfo();
        if(s==s1){
            System.out.println("建立的是同一個例項");
        }else if(s!=s1){
            System.out.println("建立的不是同一個例項");
        }else{
            System.out.println("application error");
        }
    }
}

執行結果:

劉德華
張信哲
output message: 張信哲
output message: 張信哲
建立的是同一個例項

示例二:

這一個產生隨機數的例子,整個應用程式中只需要一個類的例項來產生隨機數,客戶端程式從類中獲取這個例項,呼叫這個例項的方法nextInt(),公用的方法訪問需要進行同步,這是單例模式需要解決的同步問題。

參與者:Singleton:定義一個Instance操作,允許客戶訪問它的唯一例項,Instance是一個類操作。可能負責建立自己的唯一例項。
協作關係:客戶只能透過Singleton的Instance操作訪問一個Singleton的例項。

單例模式中需要解決的重要問題是方法的同步問題,同步的粒度有多大等。在本例子中同在獲得類的例項的時候使用了同步,程式碼如下:

package com.yunhe.singleton;

import java.util.Random;

public class Singleton {
    private Random generator;
    private static Singleton instance;

    private Singleton() {
        generator = new Random();
    }

    public void setSeed(int seed) {
        generator.setSeed(seed);
    }

    public int nextInt() {
        return generator.nextInt();

    }

    public static synchronized Singleton getInstance() {
        if (instance == null) {
            instance = new Singleton();

        }
        return instance;

    }

}

客戶端呼叫程式碼:

package com.yunhe.singleton;

public class Client{

    public static void main(String[] args){

       Singleton s1 = Singleton.getInstance();

       System.out.println(s1.toString());

       for(int i=0;i<10;i++){

           Singleton s2 = Singleton.getInstance();

           System.out.println("The randomed number is "+s2.toString());

       }

    }

}

執行結果:

com.yunhe.Singleton@61de33
The randomed number is com.yunhe.Singleton@61de33
The randomed number is com.yunhe.Singleton@61de33
The randomed number is com.yunhe.Singleton@61de33
The randomed number is com.yunhe.Singleton@61de33
The randomed number is com.yunhe.Singleton@61de33
The randomed number is com.yunhe.Singleton@61de33
The randomed number is com.yunhe.Singleton@61de33
The randomed number is com.yunhe.Singleton@61de33
The randomed number is com.yunhe.Singleton@61de33
The randomed number is com.yunhe.Singleton@61de33

 

總結:單例模式除了上述提到的常見應用之外,在實際程式設計中應用的也非常廣泛,可以根據需要應用該模式,學以致用才是最重要的。使得應用程式在執行時保持只能有一個例項,在一些大的應用程式中,主程式只需要有一個,因此需要使用單例模式

 

 

 

相關文章