1. 程式人生 > >2-1 單例模式

2-1 單例模式

pub ron 實踐 script 線程 性能 image log 生命

單例模式

一、引入

皇帝與臣子

一個類只能生成一個對象(皇帝),其他所有類對這個對象的依賴都是同一個,體現到代碼上如下:

/**
 * @Author: Zephyr
 * @Description: 定義一個私有的構造器,Emperor自己可以new一個對象,但其他類不能new當前對象,其他類只能通過靜態的getInstance方法獲取Emperor對象
 * @Date: 2018/3/1 19:09
 */
public class Emperor {
    private static final Emperor emperor= new Emperor();

    //私有構造器
    private Emperor() { }

    //靜態實例獲取
    public static Emperor getInstance(){
        return emperor;
    }

    public static String say(){
        return "無事退朝";
    }
}

/**
 * 每天臣子都會面對同一個皇帝
 */
class Minister{
    public static void main(String[] args) {
        for (int i = 0; i < 3; i++) {
            Emperor e = Emperor.getInstance();
            System.out.println("上朝第"+(i+1)+"天,皇帝說"+e.say());
        }
    }
}
  • 執行結果:

技術分享圖片

二、定義

  • 關鍵詞:【自行實例化】、【Cloneable接口】

定義:

Ensure a class has only one instance,and provide a global point of access to it
確保每個類只有一個實例,自行實例化並向整個系統提供這個實例

技術分享圖片

代碼清單7-3

/**
 * @Author: Zephyr
 * @Description: 代碼清單7-3 單例模式通用代碼(餓漢式:類加載時就創建實例,線程安全)
 * @Date: 2018/3/1 19:49
 */
public class HungrySingleton {
    private static final HungrySingleton s = new HungrySingleton();

    //私有構造器,其他類無法new當前類實例
    private HungrySingleton() {
    }

    //通過此方法向外部提供當前類實例
    public static final HungrySingleton getInstance(){
        return s;
    }

    //類中的其他方法,盡量用static
    //todo 為什麽盡量用static修飾?
    public static void otherMethod(){

    }
}

三、應用

優點

  • 減少內存開支(比如:需要頻繁創建或者銷毀一個對象)
  • 減少系統的性能開銷(比如:產生一個對象需要較多的資源)
  • 避免對資源的多重占用(比如:IO流操作過程中,只存在一個inputStream實例,則可以避免對同一個文件同時進行操作)
  • 可以用於在系統中設置全局訪問點,優化和共享資源訪問(比如:系統全局的計數器,整個系統共用)

缺點

  • 拓展困難(沒有接口,只能通過修改代碼實現修改)
  • 對測試不利(單例類沒有完成則無法進行測試,沒有接口也不能使用mock方式創建虛擬對象)
  • 與單一職責原則相違背(一個類應該只實現一個邏輯,而不應該關心其是否單例,是否單例應該取決於業務環境)

使用場景舉例

  • 要求生成唯一序列號的環境
  • 整個系統中需要一個共享數據點或者共享數據(比如:全局計數器)
  • 創建一個對象需要太多的資源
  • 需要定義大量的靜態常量和靜態方法(比如:工具類,也可以直接全部聲明為static)

註意事項
在高並發環境下可能引發線程安全問題,推薦使用餓漢式(解決方式:添加關鍵字synchronized)

/**
 * @Author: Zephyr
 * @Description: 代碼清單7-4 線程不安全的單例(懶漢式:用到時再創建實例,線程不安全)
 * @Date: 2018/3/1 20:15
 */
public class LazySingleton {
    private static LazySingleton s = null;
    
    private LazySingleton() {
    }
    
    //public static final 【synchronized】 LazySingleton getInstance(){
    public static final LazySingleton getInstance(){
        if(s==null){
            s = new LazySingleton();
        }
        return s;
    }
}
  • 單例類不要實現Clonable接口(clone()方法的調用不通過構造器,因此即使僅有私有構造器,如果實現了Clonable接口,仍然可以通過clone()方法復制對象從而產生多個實例)

四、擴展

有上限的多例模式:有固定數量實例的類。
核心邏輯:首先通過靜態代碼塊生成指定數量的對象存入私有列表,再通過getInstance()方法隨機返回列表中的某個對象
對應代碼實現如圖(代碼中使用了ArrayList存放實例,考慮到線程安全問題可以使用):

/**
 * @Author: Zephyr
 * @Description: 代碼清單7-6 臣子參拜皇帝的過程(有上限的多例模式)
 * @Date: 2018/3/1 20:29
 */
public class EmperorExpend {
    //對象屬性
    private String name;

    //設置實例數量
    private static Integer maxNum=3;

    //私有構造器
    private EmperorExpend(String name) {
        this.name = name;
    }

    //對象(皇帝)列表
    private static List<EmperorExpend> emperorList = new ArrayList<EmperorExpend>();

    //靜態代碼塊,用於產生固定數量的對象(皇帝)
    static{
        for (Integer i = 0; i < maxNum; i++) {
            emperorList.add(new EmperorExpend("皇帝0"+(i+1)));
        }
    }

    //隨機參見一個皇帝
    public static EmperorExpend getInstance(){
        Random r = new Random();
        return emperorList.get(r.nextInt(maxNum));
    }

    //皇帝發話
    public String say() {
        return name;
    }
}

class MinisterExpend{
    public static void main(String[] args) {
        //有10個大臣
        int ministerNum=10;
        for (int i = 0; i < ministerNum; i++) {
            EmperorExpend e = EmperorExpend.getInstance();
            System.out.println("第"+(i+1)+"個大臣參拜的是:"+e.say());
        }
    }
}

效果如圖:

技術分享圖片

五、最佳實踐

單例模式是23個設計模式中最簡單的模式,應用也非常廣泛。Spring中默認每個Bean都是單例的,優點是這樣Spring就可以管理這些Bean的生命周期。
如果設置了非單例模式(Prototype類型),則Bean初始化後的管理交給J2EE容器,Spring容器不再跟蹤管理這些Bean的生命周期

2-1 單例模式