Java設計模式之單利模式(Singleton)

單利模式的應用場景:spring

  單利模式(Singleton Pattern)是指確保一個類在任何狀況下都絕對只有一個實例。並提供一個全局反訪問點。單利模式是建立型模式。單利模式在生活中應用也很普遍,好比公司CEO只有一個,部門經理只有一個等。JAVA中ServletCOntext,ServetContextCOnfig等,還有spring中ApplicationContext應用上下文對象,SessionFactory,數據庫鏈接池對象等。使用單利模式能夠將其常駐於內存,能夠節約更多資源。數據庫

寫法:安全

  1:懶漢模式(線程不安全)多線程

/**
 * 線程不安全的懶漢式單利模式
 * 
 * Created by gan on 2019/11/17 17:33.
 */
public class LazySingleton {
    private static LazySingleton instance;

    //構造方法私有化
    private LazySingleton() {
    }

    public static LazySingleton getInstance() {
        if (instance != null) {
            instance = new LazySingleton();
        }
        return instance;
    }
}

  上面的代碼,提供一個靜態對象instance,構造函數私有化防止外部建立對象,提供一個靜態的getInstance方法來給訪問者一個單利對象。這種寫法的缺點就是沒有考慮到線程安全問題,當多個訪問者同時訪問的時候頗有可能建立多個對象。之因此叫懶漢式,是由於這種寫法是使用的時候才建立,起到了懶加載Lazy loading的做用,實際開發中不建議採用這種寫法。併發

  2:線程安全的懶漢式(加鎖)ide

/**
 * 線程安全的懶漢式單利模式
 * 
 * Created by gan on 2019/11/17 17:33.
 */
public class LazySingleton {
    private static LazySingleton instance;

    //構造方法私有化
    private LazySingleton() {
    }

    public synchronized static LazySingleton getInstance() {
        if (instance != null) {
            instance = new LazySingleton();
        }
        return instance;
    }
}

  這種寫法就是在第一種的基礎上添加了synchronized關鍵字保證了線程安全。這種寫法在併發高的時候雖然保證了線程安全,可是效率很低,高併發的時候全部訪問的線程都要排隊等待,因此實際開發中也不建議採用。函數

  3:惡漢式(線程安全)高併發

/**
 * 餓漢式(線程安全)
 * Created by gan on 2019/10/28 22:52.
 */
public class HungrySigleton {

    public static final  HungrySigleton  instance = new HungrySigleton();

    private HungrySigleton(){}

    public static HungrySigleton getInstance(){
        return instance;
    }
}

  直接在運行(加載)這個類的時候建立了對象,以後直接訪問。顯然這種方式沒有起到Lazy loading的效果。可是是線程安全的,實際開發中仍是比較經常使用。spa

  4:靜態內部類(線程安全)線程

/**
 * 靜態內部類方式
 * Created by gan on 2019/11/17 17:46.
 */
public class StaticInnerClassSingleton {

    //構造方法私有化
    private StaticInnerClassSingleton() {}

    //內部類
    private static class HolderInnerClass {
        //須要提供單利對象的外部類做爲靜態屬性加載的時候就初始化
        private static StaticInnerClassSingleton instance = new StaticInnerClassSingleton();
    }

    //對外暴漏訪問點
    public static StaticInnerClassSingleton getInstance() {
        return HolderInnerClass.instance;
    }
}

  這種內部類跟餓漢式單利有不少類似的地方,相比餓漢式單利模式的區別也是好處在於:靜態內部類不在單利類加載時就加載,而是在調用getInstance()方法的時候才進行加載,達到了相似於懶漢式的效果,並且這種方法又是線程安全的。實際開發中也建議採用。

  5:枚舉方法單利(線程安全)

/**
 * 枚舉單利模式
 * Created by gan on 2019/11/17 17:57.
 */
public enum EnumSingleton {
    INSTANCE;

    public void otherMetthod() {
        System.out.println("須要單利對象調用的方法。。。");
    }
}

  Effective Java做者Josh Bloch提倡的方式,好處有以下:

  1:自由串行化。

  2:保證了一個實例

  3:線程安全

  這種方式防止了單利模式被破壞,並且簡潔寫法簡單,並且絕對的線程安全,可是有個缺點就是不能繼承。

  6:雙重檢查法(一般線程安全,低機率不安全)

/**
 * Double check
 * Created by gan on 2019/11/17 18:03.
 */
public class DoubleCheckSingleton {
    private static DoubleCheckSingleton instance;

    private DoubleCheckSingleton() {}

    public static DoubleCheckSingleton getInstance() {
        if (instance == null) {
            synchronized (DoubleCheckSingleton.class) {
                if (instance == null) {
                    instance = new DoubleCheckSingleton();
                }
            }
        }
        return instance;
    }
}

  上面的這種寫法在併發極高的時候也可能會出現問題(固然這種機率很是小,可是畢竟仍是有的嘛),解決的方案就是給instance的聲明加上volatile關鍵字便可。因而就出現了下面第7總寫法。

  7:Double check(volatile)

/**
 * Double check volatile
 * Created by gan on 2019/11/17 18:03.
 */
public class DoubleCheckSingleton {
    private volatile static DoubleCheckSingleton instance;

    private DoubleCheckSingleton() {}

    public static DoubleCheckSingleton getInstance() {
        if (instance == null) {
            synchronized (DoubleCheckSingleton.class) {
                if (instance == null) {
                    instance = new DoubleCheckSingleton();
                }
            }
        }
        return instance;
    }
}

volatile關鍵字的其中一個做用就是禁止指令重排序,把instance聲明volatile後,對它的操做就會有一個內存屏障(什麼是內存屏障?),這樣在賦值完成以前,就不會調用讀操做。這裏具體的緣由網上也是衆說紛紜,這裏不進行具體闡述。

  8:ThreadLocal實現單利模式(線程安全)

/**
 * ThreadLocal實現單利模式
 * Created by gan on 2019/11/17 18:17.
 */
public class ThreadLocalSingleton {

    private static final ThreadLocal<ThreadLocalSingleton> threadLocal = new ThreadLocal() {
        @Override
        protected ThreadLocalSingleton initialValue() {
            return new ThreadLocalSingleton();
        }
    };

    private ThreadLocalSingleton(){}

    public static ThreadLocalSingleton getInstance(){
        return threadLocal.get();
    }
}

  ThreadLocal會爲每一個線程提供一個獨立的變量副本,從而隔離了多個線程堆數據的訪問衝突。對於多線程資源共享問題,同步機制採用了「以時間換空間」的方式,而ThreadLocal則採用了「以空間換時間」的方式(主要就是避免了加鎖排隊)。 前者提供一份變量,讓不一樣的線程排隊訪問,然後者爲每個線程提供了一份變量,所以能夠同時訪問而互不影響。可是實際是建立了多個單利對象的。

 

單利模式的破壞:

  1:序列化破壞

    一個對象建立好之後,有時候須要將對象序列化而後寫入磁盤。下次在從磁盤中讀取並反序列化,將其轉化爲內存對象。反序列化後的對象會從新分配內存,即建立型的對象。這樣就違背了單利模式的初衷。解決這種方式的方法就是在單利類中新增一個 private Object readResolve();方法便可,具體緣由能夠看看序列化和反序列化的源碼。

  2:反射

    經過反射「暴力破解」也能破壞單利模式,具體暫時不闡述。

  3:克隆

    克隆也會破壞單利模式,具體暫時不闡述。

相關文章
相關標籤/搜索