Java設計模式之單列模式

簡言

單列模式:單一,也就是說一個類只能有一個對象。就類似於有些軟件只能打開一次,當需要保證一個對象在內存中的唯一性時,就需要引入單列模式。

實現步驟

創建單列模式分三步操作:

1.將構造函數私有化

2.在類中創建一個本類對象

3.提供一個公有的接口來返回創建的類

相關說明

我們訪問類裏面的數據時,分兩種情況:

1.通過實例化對象,然後通過對象的引用“.”出方法或屬性。

2.通過類名“.”調用類裏面靜態的方法或屬性。

當我們將類的構造方法私有化時,此時我們無法在類外實例化對象,所以只能通過第二種方法在內部創建一個static修飾的該類對象,然後定義一個static修飾的公有的方法將創建的類返回出去。static修飾的數據是跟隨整個類的,在類加載的時候跟着類一起創建,由於構造函數私有化了,所以這個類也就創建了一次。

餓漢式

創建一個Singlehungry類

package com.single_mode;
/**
 * 單列模式:一個類只能被創建一次,將類的構造方法私有化,提供公有的返回該對象的方法
 *
 *      餓漢式:加載類的時候就創建了創建了一個Single實例
 */
public class Singlehungry {
    private String name;

    private int age;

    public  String getName() {   return name;  }

    public  int getAge() {  return age;  }

    public  void setName(String name) {  this.name = name; }

    public  void setAge(int age) {  this.age = age; }

    private Singlehungry(){ }//將構造函數私有化

    private static Singlehungry single=new Singlehungry();

    public static Singlehungry getInstance(){
        return single;
    }//提供公有訪問的接口,將該對象返回出去
}

創建一個測試類Main類

package com.single_mode;

public class Main{
    public static void main(String[] args) {
        //餓漢式
        Singlehungry single1=Singlehungry.getInstance();
        single1.setName("筱靜");
        single1.setAge(20);
        System.out.println("single1:"+single1.getAge()+"  "+single1.getName());
        Singlehungry single2=Singlehungry.getInstance();
        System.out.println("single2:"+single2.getAge()+"  "+single2.getName());
    }
}

運行結果:


single1和single2是指向的同一個對象

讓我們畫張圖來了解一下:


當我們Singlehungry.加載類時,這個類的靜態屬性在方法區也就隨着加載,首先在堆區new了一個Singlehungry的對象,假設它的地址爲0x10,方法區的single保存了堆區Singlehungry對象的地址,即single=0x10。然後在棧區調用getInstance()方法,返回single,並用single1來接收,single1也就指向堆區的Singlehungry對象,然後set進行賦值。再次調用getInstance()方法時,調用之前加載好的Singlehungry對象,也就是single2=single=0x10。所以再次get時,它的值是相同的。

懶漢式

創建一個Singlelazy類

package com.single_mode;
/**
 * 懶漢式:
 */
public class Singlelazy {
    private String name;
    private int age;
    private static Singlelazy singlelazy=null;
    private Singlelazy(){ }
//    public static Singlelazy getInstence(){   //線程不安全
//        if(singlelazy==null)                
//            singlelazy=new Singlelazy();
//        return singlelazy;
//    }
    public static Singlelazy getInstence(){
        if(singlelazy==null) {
            synchronized(Singlelazy.class) {
                if (singlelazy == null)
                    singlelazy = new Singlelazy();
            }
        }
        return singlelazy;
    }
    public  String getName() {  return this.name; }
    public  int getAge() {  return age;  }
    public  void setName(String name) {   this.name = name; }
    public  void setAge(int age) {    this.age = age;  }
}

在我們用懶漢式發生了延時加載,首先方法區裏面singlelazy賦值爲null,在調用getInstence()方法時先判斷一下,然後創建對象,而餓漢式在加載類的同時single就在堆區new了一個對象,這也是它們的區別所在。


由於方法區是線程共享的,懶漢式又有if判斷然後創建對象。因此在多線程的情況下可能存在線程安全問題,需要線程同步用到synchonized關鍵字。如果將getInstence()整個函數個鎖住,開銷可能比較大,不太可取。可直接將判斷那部分鎖住即可。

或許你會問爲什需要加上兩個if判斷?

假設有A,B,C三個線程,首先A線程搶到了CPU的執行權,執行 singlelazy==null   再執行下一個singlelazy==null 並且鎖上,當A剛第二次判斷時,CPU的執行權可能被C搶到了(CPU高速不斷的切換線程,給我們的感覺每個進程異步執行),C線程進行第一層判斷,由於第二層if上鎖了,在A線程執行完畢之前 ,C線程無法繼續執行的,只能等待了。C在等待的過程中CPU可能又切到了A線程,A線程接着執行,當A線程創建對象後,把鎖打開 return singlelazy;。B線程得到了CPU的執行權,第一輪if判斷結束false 就直接return singlelazy;最後C線程的到CPU執行權,由於A線程已把鎖打開,C線程繼續剛纔的執行,進行第二次判斷false,return singlelazy;最終得到的是A線程所創建的一個對象,解決了線程互斥問題。


每日雞湯:耐得住寂寞、經得起誘惑。你會發現通往成功的道路並不擁擠,因爲能堅持到最後的沒有幾個!


Over ! 


發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章