1. 程式人生 > 實用技巧 >設計模式-單例模式

設計模式-單例模式

單例模式模式一般大家都會認為它比較簡單,其實並非你們所認為的那樣,很多情況下,單例模式會涉及到很多優化,下面給大家簡單介紹一下單例模式的幾種演變過程:

  • 餓漢模式
  • 懶漢模式
  • 懶漢模式(加鎖關鍵字 synchronized)
  • 懶漢模式(細粒度的新增synchronized)
  • 懶漢模式(雙重檢查)
  • 靜態內部類
  • 列舉類

第一種:餓漢模式 比較簡單,類載入到記憶體的時候就進行例項化,推薦使用 ,但是有人會說,既然不用幹嘛要進行例項化;

package com.dongl.singleton;

/**
 * 餓漢模式
 * 類載入到記憶體就直接例項化一個單例,JVM會保證它的執行緒安全
 * 簡單使用 推薦使用
 * 缺點:無論用到與否 類載入就會直接例項化
 * 有人就會吹毛求疵說:你不用 你例項化幹嘛?
 
*/ public class T01_Singleton { private static T01_Singleton INSTANCE = new T01_Singleton(); public T01_Singleton() { } public static T01_Singleton getInstance(){ return INSTANCE; } public static void main(String[] args) { T01_Singleton t1 = T01_Singleton.getInstance(); T01_Singleton t2
= T01_Singleton.getInstance(); System.out.println(t1 == t2); } }

第二種:懶漢模式 這種模式在多執行緒的情況下會出現問題

package com.dongl.singleton;


/**
 * 懶漢模式 lazy loading
 * 雖然達到了按需初始化的目的 但是也帶來了執行緒安全的問題
 * 在多執行緒的情況下
 */
public class T02_Singleton {
    private static T02_Singleton INSTANCE = null;

    public T02_Singleton() {
    }

    
public static T02_Singleton getInstance(){ if(INSTANCE == null){ try { Thread.sleep(1); } catch (InterruptedException e) { e.printStackTrace(); } INSTANCE = new T02_Singleton(); } return INSTANCE; } public static void main(String[] args) { // T02_Singleton t1 = T02_Singleton.getInstance(); // T02_Singleton t2 = T02_Singleton.getInstance(); // System.out.println(t1 == t2); /**建立100個執行緒 呼叫getInstance() 列印返回的物件的hashcode()*/ for (int i = 0; i < 100; i++) { new Thread(()->{ System.out.println(T02_Singleton.getInstance().hashCode()); }).start(); } } }

//執行結果 hashcode值不相同

E:\JDK\jdk1.8\bin\java.exe "-javaagent:E:\Idea\IntelliJ IDEA
1365279759
1365279759
1365279759
1945255694
226994615



第三種:懶漢模式(加鎖關鍵字 synchronized)這種方式解決了懶漢模式下多執行緒問題,但是同時帶來的問題是效率降低;

package com.dongl.singleton;


/**
 * 懶漢模式 lazy loading
 * 雖然達到了按需初始化的目的 但是也帶來了執行緒安全的問題
 * 解決辦法使用synchronized 但是帶來的問題就是效率下降
 */
public class T03_Singleton {
    private static T03_Singleton INSTANCE = null;

    public T03_Singleton() {
    }

    /**
     * 加鎖的方式有兩種 一種是對方法進行加鎖 另一種是對程式碼塊進行加鎖
     * 涉及到的無非是鎖的粒度問題
     * @return
     */
    public static /**synchronized*/ T03_Singleton getInstance(){
        synchronized (T02_Singleton.class) {
            if (INSTANCE == null) {
                try {
                    Thread.sleep(1000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                INSTANCE = new T03_Singleton();
            }
        }
        return INSTANCE;
    }

    public static void main(String[] args) {
        /**建立100個執行緒 呼叫getInstance() 列印返回的物件的hashcode()*/
        for (int i = 0; i < 100; i++) {
            new Thread(()->{
                System.out.println(T03_Singleton.getInstance().hashCode());
            }).start();
        }
    }
}

第四種:懶漢模式(細粒度的新增synchronized)試圖通過減小鎖的粒度來進行改善效率的問題 但是不可行;

package com.dongl.singleton;


/**
 * 懶漢模式 lazy loading
 * 雖然達到了按需初始化的目的 但是也帶來了執行緒安全的問題
 * 解決辦法使用synchronized 但是帶來的問題就是效率下降
 *
 * 試圖通過減小鎖的粒度來進行改善效率的問題  但是不可行
 */
public class T04_Singleton {
    private static T04_Singleton INSTANCE = null;

    public T04_Singleton() {
    }

    public static /**synchronized*/ T04_Singleton getInstance(){
        if (INSTANCE == null) {
            //試圖通過減小鎖的粒度來進行改善效率的問題  但是不可行
            synchronized (T02_Singleton.class) {
                try {
                    Thread.sleep(1);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                INSTANCE = new T04_Singleton();
            }
        }
        return INSTANCE;
    }

    public static void main(String[] args) {
        /**建立100個執行緒 呼叫getInstance() 列印返回的物件的hashcode()*/
        for (int i = 0; i < 100; i++) {
            new Thread(()->{
                System.out.println(T04_Singleton.getInstance().hashCode());
            }).start();
        }
    }
}

第五種:懶漢模式(雙重檢查)這種方式解決了多執行緒帶來的問題;

package com.dongl.singleton;


/**
 * 懶漢模式 lazy loading
 * 雖然達到了按需初始化的目的 但是也帶來了執行緒安全的問題
 * 解決辦法使用synchronized 但是帶來的問題就是效率下降
 * 因為鎖的粒度很小也會帶來多執行緒問題
 * 這時可以使用雙重檢查 來避免
 */
public class T05_Singleton {
    private volatile static T05_Singleton INSTANCE = null;

    public T05_Singleton() {
    }

    public static /**synchronized*/
    T05_Singleton getInstance(){
        if (INSTANCE == null) {
            synchronized (T02_Singleton.class) {
                //雙重檢查
                if(INSTANCE == null) {
                    try {
                        Thread.sleep(1);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                    INSTANCE = new T05_Singleton();
                }
            }
        }
        return INSTANCE;
    }

    public static void main(String[] args) {
        /**建立100個執行緒 呼叫getInstance() 列印返回的物件的hashcode()*/
        for (int i = 0; i < 100; i++) {
            new Thread(()->{
                System.out.println(T05_Singleton.getInstance().hashCode());
            }).start();
        }
    }
}

第六種:靜態內部類實現懶載入(lazy loading)這種方式是最優解之一 因為在載入類的時候 不載入內部類這樣就實現了懶載入;

package com.dongl.singleton;


/**
 * 靜態內部類的法方法
 * JVM保證單例
 * 載入外部類的時候 不會載入內部類 這樣實現了懶載入
 */
public class T06_Singleton {

    public T06_Singleton() {
    }

    //靜態內部類
    private static class T06_SingletonHandler{
        private final static T06_Singleton INSTANCE = new T06_Singleton();
    }

    public static T06_Singleton getInstance(){
        T06_Singleton instance = T06_SingletonHandler.INSTANCE;
        return instance;
    }

    public static void main(String[] args) {
        /**建立100個執行緒 呼叫getInstance() 列印返回的物件的hashcode()*/
        for (int i = 0; i < 100; i++) {
            new Thread(()->{
                System.out.println(T06_Singleton.getInstance().hashCode());
            }).start();
        }
    }
}

第七種:列舉類不僅可以解決執行緒同步,還可以防止反序列化。

package com.dongl.singleton;


/**
 * 不僅可以解決執行緒同步,還可以防止反序列化。
 */
public enum  T07_Singleton {

    INSTANCE;

    public static void main(String[] args) {
        /**建立100個執行緒 呼叫getInstance() 列印返回的物件的hashcode()*/
        for (int i = 0; i < 100; i++) {
            new Thread(()->{
                System.out.println(T07_Singleton.INSTANCE.hashCode());
            }).start();
        }
    }
}

以上其中方式是按照問題出現 一步步的優化得到的,如果你覺得有疑問可以評論區說出你的觀點,一起討論一起進步!!!