設計模式專題(二)單例模式

目錄

單例模式

餓漢式單例

懶漢式單例

內部類單例

註冊登記式單例

枚舉式單例

單例模式

單例模式(一個類模板,在整個系統執行過程中,只允許產生一個實例)應用廣泛,主要應用在:

  • 配置文件
  • Ioc容器
  • 日曆
  • 工廠本身

單例模式:解決一個併發訪問的時候線程安全問題,保證單例的技術方案有很多種

餓漢式單例

在實例使用之前,不管你用不用,我都先new出來再說,避免了線程安全問題

餓漢式單例:

public class Hungry {

   //私有構造方法,防止外部new
   private Hungry(){}

   private static final Hungry hungry = new Hungry();

   public static Hungry getInstance(){
       return  hungry;
   }

}

我們來測試: 

public static void main(String[] args) {
       int count = 200;

       //發令槍,我就能想到運動員
       final CountDownLatch latch = new CountDownLatch(count);

       long start = System.currentTimeMillis();
       for (int i = 0; i < count;i ++) {
           new Thread(){
               @Override
               public void run() {
                   try{
                       try {
                           // 阻塞
                           // count = 0 就會釋放所有的共享鎖
                           // 萬箭齊發
                           latch.await();
                       }catch(Exception e){
                           e.printStackTrace();
                       }
                       //必然會調用,可能會有很多線程同時去訪問getInstance()
                       Object obj = Hungry.getInstance();
                       System.out.println(System.currentTimeMillis() + ":" + obj);
                   }catch (Exception e){
                       e.printStackTrace();
                   }
               }
           }.start(); //每循環一次,就啓動一個線程,具有一定的隨機性
           //每次啓動一個線程,count --
           latch.countDown();
       }
       long end = System.currentTimeMillis();
       System.out.println("總耗時:" + (end - start));

   }

打印結果發現,無論怎麼運行,程序始終拿到的是一個實例

懶漢式單例

默認加載的時候不實例化,在需要用到這個實例的時候進行實例化(延時加載)
懶漢式單例:

public class LazyOne {
    private LazyOne(){}
    //靜態塊,公共內存區域
    private static LazyOne lazy = null;
    
    public static LazyOne getInstance(){
        //調用方法之前,先判斷
        //如果沒有初始化,將其進行初始化,並且賦值
        //將該實例緩存好
        if(lazy == null){
            //兩個線程都會進入這個if裏面
            lazy = new LazyOne();
        }
        //如果已經初始化,直接返回之前已經保存好的結果
        return lazy;
    }
}

打印結果發現存在不同實例,說明餓漢式單例是線程不安全
怎麼變爲安全的呢!我們通常可以這樣設計餓漢式單例(在獲取實例方法上加上synchronized 鎖)

public class LazyTwo {

    private LazyTwo(){}

    private static LazyTwo lazy = null;

    public static synchronized LazyTwo getInstance(){

        if(lazy == null){
            lazy = new LazyTwo();
        }
        return lazy;
    }
}

然後我們將測試工具類實例改爲:
運行發現,無論怎麼運行,程序始終拿到的是一個實例,說明synchronized 鎖是可以保證線程安全的!
但是synchronized 性能並不是最好的鎖!

我們看這樣的測試:

public static void main(String[] args) {
        long start = System.currentTimeMillis();
        for (int i = 0; i < 200000000;i ++) {
            Object obj = LazyTwo.getInstance();
        }
        long end = System.currentTimeMillis();
        System.out.println("總耗時:" + (end - start));
    }

運行發現每次獲取的時間性能較低,由此產生另一種單例,內部類單例模式

內部類單例

特點:

  1. 在外部類被調用的時候內部類纔會被加載
  2. 內部類一定是要在方法調用之前初始化
  3. 巧妙地避免了線程安全問題
  4. 這種形式兼顧餓漢式的內存浪費,也兼顧synchronized性能問題,完美地屏蔽了這兩個缺點
// 史上最牛B的單例模式的實現方式
public class LazyThree {

    private boolean initialized = false;

    //每一個關鍵字都不是多餘的
    //static 是爲了使單例的空間共享
    //保證這個方法不會被重寫,重載
    public static final LazyThree getInstance(){
        //在返回結果以前,一定會先加載內部類
        return LazyHolder.LAZY;
    }

    //默認不加載
    private static class LazyHolder{
        private static final LazyThree LAZY = new LazyThree();
    }

}

註冊登記式單例

  • 每使用一次,都往一個固定的容器中去註冊並且將使用過的對象進行緩存,下次取對象就直接從緩存中取值,以保證每次獲取的都是同一個對象
  • IOC中的單例模式,就是典型的註冊登記式單例

基於map形式的註冊單例:

public class RegisterMap {

   private RegisterMap(){}

   private static Map<String,Object> register = new ConcurrentHashMap<String,Object>();

   public static RegisterMap getInstance(String name){
       if(name == null){
           name = RegisterMap.class.getName();
       }

       if(register.get(name) == null){
           try {
               register.put(name, new RegisterMap());
           }catch(Exception e){
               e.printStackTrace();
           }
       }
       return (RegisterMap)register.get(name);
   }

}

枚舉式單例

使用常量值來保證對象的唯一,實際上就是註冊登記式的一種


public enum DataSourceEnum {
    DATASOURCE;
    private DBConnection connection = null;
    private DataSourceEnum() {
        connection = new DBConnection();
    }
    public DBConnection getConnection() {
        return connection;
    }
}

 

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