java設計模式-單例模式

單例模式

 概述

  單例模式是 Java 比較簡單,也是最基礎,最常用的設計模式之一。在運行期間,保證某個類只創建一個實例,保證一個類僅有一個實例,並提供一個訪問它的全局訪問點。

  單例模式主要有餓漢式單例懶漢式單例靜態內部類實現單例枚舉類實現單例等,不同的方式有不同的優缺點,下面介紹各個實現方式和優缺點。

餓漢式單例

  創建餓漢式單例簡單粗暴,在類被虛擬機加載時就創建類對象;

  缺點:可能在還不需要此實例的時候就已經把實例創建出來了,沒起到lazy loading的效果;

  優點:實現簡單,而且安全可靠;

package pattern.single;
/**
 * 餓漢式單例模式 不存在線程安全問題,因為在類加載時就創建好了對象
 * @author ningbeibei
 */
public class HungrySingle {
    // 當類加載時創建對象
    private static HungrySingle hungery = new HungrySingle();
    // 私有化構造函數,屏蔽外部創建對象
    private HungrySingle() {
    }
    // 提供全局訪問點,直接返回實例對象
    public static HungrySingle getHungrySingle() {
        return hungery;
    }
}

  測試類代碼

package pattern.single;
/**
 * 餓漢式單例模式測試類
 * @author ningbeibei
 */
public class test {
    public static void main(String[] args) {
        //餓漢式單例模式,獲取 HungrySingle 實例
        HungrySingle hungry = HungrySingle.getHungrySingle();
        System.out.println("HungrySingle對象:"+hungry);
    }
}

  運行結果

 

 懶漢式單例

   相比餓漢式,懶漢式實現了用則創建不用則不創建,真正實現了懶加載效果;

package pattern.single;
/**
  *  懶漢式單例模式
 * @author ningbeibei
 */
public class SinglePattern {
    //聲明對象變量
    private static SinglePattern single = null;
    //私有化構造函數
    private SinglePattern(){
    }
    //對外提供獲取對象得方法
    public static SinglePattern getSinglePattern() {
        if(single==null){
            return new SinglePattern();
        }
        return single;
    }
}

  注意:上面代碼實現了懶漢式單例,getSinglePattern()方法先判斷實例是否為空再決定是否去創建實例,看起來似乎很完美,但是存在線程安全問題。在並發獲取實例的時候,可能會存在構建了多個實例的情況。所以,需要對此代碼進行下改進,確保實例唯一。

   改進後代碼

package pattern.single;
/**
 * 雙重檢查加鎖
 * 線程安全懶漢模式
 * @author ningbeibei
 */
public class LockSinglePattern {
    //聲明變量, 使用volatile關鍵字確保絕對線程安全
    private volatile static LockSinglePattern lockSingle =null;
    //私有化構造函數
    private LockSinglePattern() {
    }
    //提供全局唯一獲取實例方法
    public static LockSinglePattern getLockSinglePattern() {
        //判斷實例是否null
        if(lockSingle==null) {
            //對單例類進行加鎖
            synchronized (LockSinglePattern.class) {
                //在判斷是否為null
                if(lockSingle==null) {
                    //創建實例
                    lockSingle = new LockSinglePattern();
                }
            }
        }
        //返回實例
        return lockSingle;
    }
}

  注意:這裡採用了雙重校驗的方式,對懶漢式單例模式做了線程安全處理。通過加鎖,可以保證同時只有一個線程走到第二個判空代碼中去,這樣保證了只創建 一個實例。這裡還用到了volatile關鍵字來修飾lockSingle,其最關鍵的作用是防止指令重排。

  測試類

package pattern.single;
/**
 * 懶漢式單例模式測試類
 * @author ningbeibei
 */
public class test {
    public static void main(String[] args) {
        //雙重加鎖,線程安全懶漢式單例模式
        LockSinglePattern lock= LockSinglePattern.getLockSinglePattern();
        System.out.println("線程安全懶漢式單例模式:"+lock);
    }
}

  運行結果

 

 

 

靜態內部類

  通過靜態內部類的方式實現單例模式是線程安全的,因為內部靜態類只會被加載一次,故該實現方式是線程安全的

  代碼如下:

package pattern.single;
/**
 * 靜態內部類實現單例模式
 * @author ningbeibei
 */
public class InteriorSingle {
    /**
     * 靜態內部類
     * @author ningbeibei
     */
    private static class Insingle {
        //靜態初始化器,由jvm來報證線程安全
        private static InteriorSingle single = new InteriorSingle();
    }
    //私有化構造函數
    private InteriorSingle() {
    }
    //提供全局唯一訪問點
    public static InteriorSingle getSingle() {
        return Insingle.single;
    }
}

   注意:通過靜態內部類的方式實現單例模式是線程安全的,同時靜態內部類不會在InteriorSingle類加載時就加載,而是在調用getSingle()方法時才進行加載,達到了懶加載的效果。似乎靜態內部類看起來已經是最完美的方法了,其實不是,可能還存在反射攻擊或者反序列化攻擊。

   測試類

package pattern.single;
/**
 * 懶漢式單例模式測試類
 * @author ningbeibei
 */
public class test {
    public static void main(String[] args) throws Exception {
        //靜態內部類實現單例模式。這個也式線程安全得
        InteriorSingle Interior = InteriorSingle.getSingle();
        System.out.println("靜態內部類單例模式:"+Interior);
    }
}

  運行結果

 

 

 枚舉實現單例模式

   最佳的單例實現模式就是枚舉模式。利用枚舉的特性,讓JVM來幫我們保證線程安全和單一實例的問題。而且寫法還特別簡單。

   代碼如下

package pattern.single;
/**
 * 枚舉類
 * @author ningbeibei
 */
public enum TypeSingle {
    // 定義一個枚舉的元素,它 就代表了Singleton的一個實例
    TYPESINGLE;
    //業務方法
    public void get() {
        System.out.println("枚舉中的方法");
    }
}

  測試代碼

package pattern.single;
/**
 * 懶漢式單例模式測試類
 * @author ningbeibei
 */
public class test {
    public static void main(String[] args) throws Exception {
        //枚舉實現單例模式
        TypeSingle.TYPESINGLE.get();
    }
}

  運行結果

 

 

 

破壞單例的兩種方式

  反射破壞單例;

  序列化破壞單例;

  這兩中破壞單例模式的方式不會對枚舉單例方式構成威脅,所以一般都推薦枚舉實現單例模式;

總結

  以上列舉了多種單例模式的寫法,在不同的場景中有不同應用;

  寫的不足之處還望指正以便我及時更正避免讀者誤解;