1. 程式人生 > >Java設計模式學習--單例模式

Java設計模式學習--單例模式

單例模式

  核心作用 保證一個類只有一個例項 , 並且提供一個訪問該實習的全域性訪問點 . 

 

常見的應用場景 :

  • Windows 的 Tsask Manager (工作管理員) 就是典型的單例模式 當你開啟工作管理員的時候,只要你能不關閉工作管理員頁面,你電腦只能顯示一個工作管理員頁面 。
  • 專案中 , 讀取配置檔案的類 , 一般也只有一個物件。 沒有必要每次使用配置檔案資料,每次 new 一個物件去讀取。
  • 資料庫連線池的設計一般也是採用單例模式 , 因為資料庫連線是一種資料庫資源。
  •  在 Spring 中 , 每個 Bean 預設就是單例的 , 這樣做的優點是 Spring 容器可以管理 。
  •  在 Spring MVC 框架愛/struts1 框架中 , 控制器物件也是單例的 。

單例模式有什麼優點呢?

  1. 由於單例模式只生成一個例項,減少了系統性能開銷 , 當一個物件的產生需要比較多的資源時 , 如 讀取配置 , 產生其他依賴物件時 , 則可以通過在應用啟動時直接產生一個單例物件, 然後永久駐留記憶體的放式解決 。
  2. 單例模式可以在系統設定全域性的訪問點 , 優化環共享資源訪問 , 例如 可以設計一個單例類, 負責所有資料表的對映處理 。 

常見單例模式的四種實現放式

 

1), 餓漢式單例模式

 

/**
 * 餓漢式單例模式
 *
 * 當類初始化的時候,進行建立當前類的例項 執行緒安全  沒有延遲載入
 */
public class SingletionDemo {

    //1 宣告一個私有的靜態變數
    private static SingletionDemo instance=new SingletionDemo();
    
    //2 私有化構造器 避免外部直接建立物件
    private SingletionDemo(){}
    
    //3 建立一個對外的公共的靜態方法 訪問該變數 
    //方法沒有同步  效率高
    public static SingletionDemo getInstance() {
        return instance;
    }
}

餓漢式單例模式總結如下:

  •  在程式碼上, static 變數會在類裝載時初始化 , 此時也不會涉及多個執行緒訪問該物件的問題 。虛擬機器保證只會裝載一次該類 , 肯定不會發生併發訪問的問題 。 因此, 可以省略Synchronized 關鍵字 。
  • 也存在這樣一個問題: 如果只是載入本類 , 而不是呼叫getInstance() , 甚至永遠沒有呼叫 ,則會造成資源浪費 !

 

2),懶漢式單例模式

 

/** 懶漢式 單例模式
 * @author 曉電腦
 *
 * 單執行緒狀態下是隻是一個例項物件 如果多個執行緒訪問則會出現問題
 * 多執行緒則會是多個物件  需要加上synchronized
 */
public class SingletionDemo01 {

    //1 宣告一個私有的靜態變數  
    private static SingletionDemo01 instance;

    //2 私有構造器 避免外部直接建立物件
    private SingletionDemo01(){}

    //3 建立一個對外的公共的靜態方法  訪問該變數 如果變數沒有物件 建立物件
    public static SingletionDemo01 getInstance() throws InterruptedException {
        if (null == instance){
            instance=new SingletionDemo01();
        }
        return instance;

    }
}

 

如果我們使用main方法進行測試如下

 public static void main (String[] args) throws InterruptedException {
        System.out.println("_________單執行緒狀態下_________");
        System.out.println(SingletionDemo01.getInstance());
        System.out.println(SingletionDemo01.getInstance());
    }

main是一個單執行緒, 所以 上面列印的倆個地址是一樣的 , 如果模擬多執行緒的情況下,則會出現問題 ,我們改動懶漢單例模式如下

 

/** 懶漢式 單例模式
 * @author 曉電腦
 *
 * 單執行緒狀態下是隻是一個例項物件 如果多個執行緒訪問則會出現問題
 * 多執行緒則會是多個物件  需要加上synchronized
 */
public class SingletionDemo01 {

    //1 宣告一個私有的靜態變數  
    private static SingletionDemo01 instance;

    //2 私有構造器 避免外部直接建立物件
    private SingletionDemo01(){}

    //3 建立一個對外的公共的靜態方法  訪問該變數 如果變數沒有物件 建立物件 
    //需要在static 後面加上synchronized 
    public static SingletionDemo01 getInstance(Long time) throws InterruptedException {
        if (null == instance){
             // 加入延遲時間 time
            Thread.sleep(time);
            instance=new SingletionDemo01();
        }
        return instance;

    }
}

 

接下來建立一個執行緒,對懶漢單例模式進行訪問

 

    /**
     * 建立一個執行緒 來執行懶漢單例模式
     */
    static class ThreadDemo extends Thread{
        private Long time;
        
        //構造器對延遲時間進行初始化
        public ThreadDemo(Long time){
            this.time=time;
        }
        @Override
        public void run() {
            try {
                System.out.println(Thread.currentThread().getName() + "---->" + SingletionDemo01.getInstance(time));
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }

 

main方法進行測試多執行緒情況下訪問懶漢單例模式

    public static void main (String[] args) throws ClassNotFoundException, InterruptedException {
        System.out.println("_________單執行緒狀態下_________");
//        System.out.println(SingletionDemo01.getInstance());
//        System.out.println(SingletionDemo01.getInstance());
        System.out.println("______模擬多執行緒_______");
        //設定延遲時間
        ThreadDemo demo = new ThreadDemo(100L);
        //設定執行緒名稱
        demo.setName("zs");
        //啟動執行緒
        demo.start();

        ThreadDemo demo1 = new ThreadDemo(300L);
        demo1.setName("www");
        demo1.start();
        
    }

 

執行結果如下

     

 

總結 :

  • 懶漢單例模式,需要加上 Synchronized 同步鎖 ,才能再多執行緒的情況下也是同一個物件,否則只是單執行緒下是同一個物件而多執行緒情況下,是不同的物件 。
  • Lazy load ! 延遲載入, 懶載入 ! 真正用的時候才載入 !
  • 也有一個問題: 資源利用率高了 。 但是 , 每次呼叫getInstance()方法都要同步,併發效率較低 。

 

3),靜態內部類實現放式(也是一種懶載入放式)

 

/**靜態內部類實現方式
 * @author 曉電腦
 */
public class StaticSingletion {
    //1 私有化構造器 避免外部類直接建立物件
    private StaticSingletion(){
    }

    //2 建立static 內部類
    private static class DemoStatic{
        private static final StaticSingletion instance=new StaticSingletion();
    }
    //3 提供外部訪問的入口
    public static StaticSingletion getInstance(){
        return DemoStatic.instance;
    }
}

總結 :

  • 外部類沒有 static 屬性 ,則不會像餓漢式那樣立即載入物件 。
  • 只有真正呼叫了 getInstance() 方法,才會載入靜態內部類 。載入類時是執行緒安全的 。 instance 是static final 型別 ,保證了記憶體中只有這樣的一個例項存在 ,而且 只能被賦值一次 ,從而保證了執行緒安全性 。
  • 兼備了併發高效呼叫和延遲載入的優勢 。

 

4),使用列舉的方式實現單例模式

 

 

package com.tuogo.instance;

/**
 * 使用列舉來實現單例
 */
public enum EnumSingletion {
    INSTANCE;

    //1 建立私有屬性
    private EnumSingletionDemo02 instance;

    //2 私有構造器 類載入的時候給這個屬性賦值
    private EnumSingletion(){
        instance=new EnumSingletionDemo02();
    }
    //3 提供外界訪問的入口
    public EnumSingletionDemo02 getInstance() {
        return instance;
    }
}

/**要實現單例的類
 */
class EnumSingletionDemo02{

}

 

main測試類程式碼 如下

 

    public static void main (String[] args) {
        EnumSingletionDemo02 instance1 = EnumSingletion.INSTANCE.getInstance();
        EnumSingletionDemo02 instance2 = EnumSingletion.INSTANCE.getInstance();
        EnumSingletionDemo02 instance3= EnumSingletion.INSTANCE.getInstance();

        System.out.println(instance1);
        System.out.println(instance2);
        System.out.println(instance3);
    }

總結:

  • 實現簡單
  • 列舉本身就是單例模式。 由JVM從根本上提供保障! 避免了通過反射和反序列化的漏洞!
  • 缺點: 無延遲載入 

 

5),雙重檢測鎖實現 (不推薦使用)

  • 因為 由於編譯器優化原因和JVM底層內部模型原因偶爾會出現問題 , 不建議使用 。 
  • 這裡就不做具體的展現

 

那我們如何選用適合哪一種單例模式呢?

  • 單例物件 佔用 資源少  , 不需要  延遲載入 :  列舉類  好與 餓漢式
  • 單例物件 佔用  資源大 ,    需要   延遲載入 :  靜態內部類式單利模式  好與 懶漢式    

 

拓展

    IDEA怎麼生成UML類圖?(也就是對於已有的程式碼,想將相關類繪製成UML類圖)

     第一步

  File -> Setting->Tools -> JAVA class Diagrams

      第二步

 

第三步生成類圖

 

在你想生成類圖的類右鍵 -> Diagrams -> Show Diagram...

 

如圖

 

這時你可以左鍵類名不丟,也可以把類拖進來