1. 程式人生 > >Java設計模式1:單例模式

Java設計模式1:單例模式

咳咳,想系統的整理一下知識想了好久了,畢竟工作了快半年了,業務程式碼感覺已經寫得差不多了,明顯感覺到又到了再夯實一遍基礎的時候了,畢竟基礎打得好後面才能得心應手,事半功倍。所以就從設計模式這裡開始看吧。

 

設計模式感覺在寫程式碼的時候也是挺重要的,確實有些時候就是不知道該如何設計自己的程式碼,所以這次就從這裡入手啦。話雖如此我也不打算全寫,就挑著常用的來寫吧,感覺全都寫了還是有點多的。。。

 

囉嗦了一大片,現在就從最常用的單例模式開始寫吧。

 

單例模式是什麼

簡單來說單例模式就是一種類的寫法,它保證了你所寫的類最多隻會被例項化一個物件。

 

使用場景

單例模式只允許建立一個物件,因此節省記憶體,加快物件訪問速度,因此物件需要被公用的場合適合使用,如多個模組使用同一個資料來源連線物件等等。如: 

    1.需要頻繁例項化然後銷燬的物件。 

    2.建立物件時耗時過多或者耗資源過多,但又經常用到的物件。 

    3.有狀態的工具類物件。 

    4.頻繁訪問資料庫或檔案的物件。 

以下都是單例模式的經典使用場景: 

    1.資源共享的情況下,避免由於資源操作時導致的效能或損耗等。如上述中的日誌檔案,應用配置。 

    2.控制資源的情況下,方便資源之間的互相通訊。如執行緒池等。 

 

實現方法

首先說一下所有寫法的共通之處,也是單例模式要想保證唯一物件並正常使用的必要寫法:

1.構造器私有

2.自己建立自己的例項化物件

3.提供給外界獲得此物件的方法

通過構造器私有,我們的類將不能夠被外界例項化,同時外界只能通過本類提供的方法來獲取由本類已經建立好的物件。

 

首先是最常見的兩種方法,懶漢式和餓漢式:

 

首先是餓漢式:

 

/** 餓漢式*/

public class Singleton {

    private static Singleton singleton = new Singleton();

    private Singleton(){}

    public static Singleton getInstance(){

        return singleton;

    }

    public void sayHello(){

        System.out.println("hello world !");

    }

}

 

接下來是懶漢式:

 

/** 懶漢式*/

public class Singleton{

    private static Singleton singleton = null;

    private Singleton(){}

    public static Singleton getInstance() {

        if (singleton==null){

            singleton=new Singleton();

        }

        return singleton;

    }

}

 

這兩個我們對比來看,主要區別就在於new Singleton()這一步執行的時機,餓漢式在初始化系統變數的時候就進行了new這一步動作,而當需要的時候直接返回已有的物件;反過來看懶漢式則並沒有在初始化系統變數的時候建立物件,而是在呼叫的時候進行判斷,如果還沒有物件,就new一個出來,如果有就直接返回去,因此懶漢式總是在第一次呼叫的時候建立物件,以後一直返回這個物件。

 

這兩種方法各有各的側重點,餓漢式由於是在載入類的時候進行的物件建立,但是該物件可能在接下來的一段時間內並沒有使用,所以會造成資源的浪費。而懶漢式在使用的時候進行建立,也就保證了不會白白浪費掉,但是建立物件畢竟會花費時間,所以又回到了計算機的一個永恆不變的話題,是時間換空間,還是空間換時間?

 

除了對於空間和時間的傾向性不同,還有一個問題需要我們考慮,那就是執行緒安全的問題。

 

對於餓漢式來說,由於虛擬機器在載入類的時候會自動保證只有一個執行緒來處理,所以餓漢式是執行緒安全的;但是對於懶漢式來說,有可能出現在呼叫時,當第一個執行緒判斷為空,進入了建立物件的步驟,這時候執行緒切換,第二個執行緒也執行到這裡,由於上一個執行緒還沒能創建出來物件,所以第二個執行緒也判斷為空,進入了建立執行緒的操作,這樣便會導致我們的單例不再”單例“。

 

既然知道了癥結在哪裡,那我們就對症下藥就好了,我們知道通過synchronized關鍵字可以進行執行緒的同步,所以我們在可能產生問題的部分,也就是為空判斷上加上執行緒同步:

/**

 * 懶漢式(加執行緒同步)

 */

public class Singleton {

    private static Singleton singleton = null;

    private Singleton() {}

    public static Singleton getInstance() {

        synchronized (Singleton.class) {

            if (singleton == null) {

                singleton = new Singleton();

            }

            return singleton;

        }

    }

}

從而保證了懶漢式的單一例項。

 

但是問題又來了,眾所周知的執行緒同步會導致效率低下,那麼有沒有辦法提高一下效率呢?通過研究之前的程式碼我們發現,所有想要使用該類的物件的地方都要通過getInstance()方法,而每次執行該方法都會直接進入synchronized片段,所以我們可以在該片段之外再加上一層判斷,判斷該類的例項是否已經初始化成功

/**

 * 懶漢式(雙重加鎖執行緒同步)

 */

public class Singleton {

    private static Singleton singleton = null;

    private Singleton() {}

    public static Singleton getSingleton() {

        if (singleton == null) {

            synchronized (Singleton.class) {

                if (singleton == null) {

                    singleton = new Singleton();

                }

                return singleton;

            }

        }

        return singleton;

    }

}

這樣在懶載入的時候我們終於能夠保證執行緒同步了,真的不容易。。。。

 

好了,上面介紹完麻煩的方法,我們來介紹一個非常巧妙的方法

 

/** 內部類方式*/

public class Singleton {

    private Singleton(){}

    private static class SingletonHolder {

        private static final Singleton instance=new Singleton();

    }

    public static Singleton getInstance(){

        return SingletonHolder.instance;

    }

    public void sayHello(){

        System.out.println("hello world !");

    }

}

 

我們可以看到在我們要實現單例的類內部實現了一個內部類,然後通過內部類來持有我們的例項物件。這種方法的巧妙之處就在於將餓漢式和懶漢式的優點都結合起來了,由於我們的內部類是在呼叫的時候才進行載入的,所以擁有懶漢式節省資源的好處,同時由於我們的例項物件是在載入內部類的過程中例項化的,又會有虛擬機器級別的執行緒安全保證,可以說是一舉兩得了。

 

除了這種方法以外,由於Java在jdk1.5之後添加了列舉型別,可以說列舉型別的特性和單例模式有異曲同工之妙了,我們可以通過構建一個只有一個例項的列舉型別來實現單例模式:

/** 內部類方式*/

public enum Singleton {

    INSTANCE;

    public void sayHello(){

        System.out.println("hello world !");

    }

}

 

只需要在列舉的時候只列舉一個例項,單例模式就自動完成了!簡單到爆炸QWQ