java設計模式1.單例模式

設計模式分類:java

  • 建立型模式:工廠模式、單例模式、建造者模式、原型模式。
  • 結構型模式:適配器模式、組合模式、裝飾器模式、代理模式、門面模式、橋接模式、享元模式。
  • 行爲型模式:策略模式、模板方法模式、觀察者模式、迭代子模式、責任鏈模式、命令模式、備忘錄模式、訪問者模式、調停者模式等。

設計模式原則:
開閉原則:對擴展開放,對修改封閉。在程序須要進行拓展的時候,不能去修改原有的代碼,而是要擴展原有代碼,實現一個熱插拔的效果。因此一句話歸納就是:爲了使程序的擴展性好,易於維護和升級,想要達到這樣的效果,咱們須要使用接口和抽象類等,後面的具體設計中咱們會提到這點。編程

  • 1.單一職責原則

不要存在多於一個致使類變動的緣由,也就是說每一個類應該實現單一的職責,不然就應該把類拆分。設計模式

  • 2.里氏替換原則

任何基類能夠出現的地方,子類必定能夠出現。里氏替換原則是繼承複用的基石,只有當衍生類能夠替換基類,軟件單位的功能不受到影響時,基類才能真正被複用,而衍生類也可以在基類的基礎上增長新的行爲。安全

里氏代換原則是對「開-閉」原則的補充。實現「開閉」原則的關鍵步驟就是抽象化。而基類與子類的繼承關係就是抽象化的具體實現,因此里氏代換原則是對實現抽象化的具體步驟的規範。里氏替換原則中,子類對父類的方法儘可能不要重寫和重載。由於父類表明了定義好的結構,經過這個規範的接口與外界交互,子類不該該隨便破壞它。併發

  • 3.依賴倒轉原則

面向接口編程,依賴於抽象而不依賴於具體。寫代碼時用到具體類時,不與具體類交互,而與具體類的上層接口交互。app

  • 4.接口隔離原則

每一個接口中不存在子類用不到卻必須實現的方法,若是否則,就要將接口拆分。使用多個隔離的接口,比使用單個接口(多個接口方法集合到一個的接口)要好。性能

  • 5.迪米特法則(最少知道原則)

一個類對本身依賴的類知道的越少越好。不管被依賴的類多麼複雜,都應該將邏輯封裝在方法的內部,經過public方法提供給外部。這樣當被依賴的類變化時,才能最小的影響該類。
最少知道原則的另外一個表達方式是:只與直接的朋友通訊。類之間只要有耦合關係,就叫朋友關係。耦合分爲依賴、關聯、聚合、組合等。咱們稱出現爲成員變量、方法參數、方法返回值中的類爲直接朋友。局部變量、臨時變量則不是直接的朋友。咱們要求陌生的類不要做爲局部變量出如今類中。優化

  • 6.合成複用原則

儘可能首先使用合成/聚合的方式,而不是使用繼承。

spa

  • 單例模式

 要點:線程

  • 1.只能有一個類實例;
  • 2.類必須自行建立這個實例;
  • 3.類必須自行向整個系統提供這個實例。

常見實現方式:餓漢式、延遲加載、.延長初始化佔位類模式、枚舉方式。

  • 1. 餓漢式
public class Singleton {   
    private static Singleton = new Singleton();
    private Singleton() {}
    public static getSignleton(){
        return singleton;
    }
}

java初始器中採用了特殊的方式來處理靜態域,並提供了額外的線程安全性保證。靜態初始化器是由JVM在類的初始化階段執行,即在類被加載後且被線程使用以前。因爲JVM將在初始化期間得到一個鎖,而且每一個線程都至少獲取一次這個鎖以確保這個類已經加載,所以在靜態初始化期間,內存寫入操做將自動對全部線程可見。所以不管是在被構造期間仍是被引用時,靜態初始化的對象都不須要顯示的同步。然而,這僅適用於在構造時的狀態,若是對象是可變的,那麼在讀線程和寫線程之間仍然須要經過同步來確保隨後的修改操做是可見的,以免數據破壞。

這種在第一次引用該類的時候就建立對象實例,無論實際是否須要,其缺點在於該類加載的時候就會直接new 一個靜態對象出來,當系統中這樣的類較多時,會使得啓動速度變慢 ,因此比較適合小系統。

 

  • 2. 延遲加載(非線程安全)
public class Singleton {
    private static Singleton singleton = null;
    private Singleton(){}
    public static Singleton getSingleton() {
        if(singleton == null) 
           singleton = new Singleton();
        return singleton;
    }
}

寫法很簡單,由私有構造器和一個公有靜態工廠方法構成,在工廠方法中對singleton進行null判斷,若是是null就new一個出來,最後返回singleton對象。可是這樣有一個致命弱點:若是有兩條線程同時調用getSingleton()方法,就有很大可能致使重複建立對象。

 

  • 3. 延遲加載(線程安全)
public class Singleton {
    private static Singleton singleton;
    private Singleton(){}
    public synchronized static Singleton getSingleton(){
        if(singleton == null){
            singleton = new Singleton();
        }
        return singleton;
    }    
}

考慮線程安全,對singleton的null判斷以及new的部分使用synchronized進行加鎖。因爲getSingleton的代碼路徑很短,所以若是getSingleton沒有被多個線程頻繁調用。那麼不會存在激烈的競爭。可是早期JVM在性能上存在一些有待優化的地方(1.5及以前),同步哪怕沒有競爭的同步都存在着巨大的性能開銷。

 

  • 4. 雙重檢查鎖
public class Singleton {
 
    private static volatile Singleton singleton;
 
    private Singleton(){
 
    }
 
    public  static Singleton getSingleton(){
        if(singleton != null){
            return singleton;
        }
 
        synchronized(Singleton.class){
            if(singleton == null){
               singleton = new Singleton();
            }
        }
        return singleton;
    }    
}

因而對上一種方式進行改進,進行雙重檢查,由於在大多數狀況是獲取一個已經構造好的Resource引用,因此代碼路徑並不會涉及到同步塊,因此就避免了在同步上的消耗。可是又因爲對於實例狀態的第一次判斷訪問沒有使用同步,將會存在可見性問題,一樣是不安全的,所以須要使用volatile來保證初始化實例的可見性。

 

  • 5. 延長初始化佔位類模式
public class Singleton {
    private static class Holder {
        private static Singleton singleton = new Singleton();
    }
 
    private Singleton(){
    
    }
 
    public static Singleton getSingleton(){
        return Holder.singleton;
    }
}

使用一個專門的內部靜態類來初始化Singleton,JVM將推遲Holder的初始化操做,直到開始使用這個類時才初始化,而且因爲經過一個靜態初始化來初始化Singleton,所以不須要額外的同步。當任何一個線程第一次調用getSingleton時,都會使Holder被加載和被初始化,此時靜態初始化器將執行Singleton的初始化操做。其實就是將單例的初始化動做放在了一個被延遲了的類加載動做中,這個類加載動做保證了單例初始化的線程安全

可是,上面提到的全部實現方式都有兩個共同的缺點:須要額外的工做(Serializable、transient、readResolve())來實現序列化,不然每次反序列化一個序列化的對象實例時都會建立一個新的實例。另外阻止不了使用反射強行調用私有構造器(若是要避免這種狀況,能夠修改構造器,讓它在建立第二個實例的時候拋異常)。

 

  • 6. 枚舉方式
public enum Singleton  {
    SPRING,SUMMER,AUTUMN,WINTER;
}

使用枚舉除了線程安全和防止反射強行調用構造器以外,還提供了自動序列化機制,防止反序列化的時候建立新的對象。所以,Effective Java 也推薦使用枚舉來實現單例。關於java枚舉的介紹能夠單獨寫一個篇章了,這裏簡單介紹下枚舉類是如何作到線程安全以及防止反序列建立新對象的。

enum就和class同樣,只是一個關鍵字,他並非一個類,那麼枚舉是由什麼類維護的呢,可使用反編譯進行查看:

public final class T extends Enum
{
    private T(String s, int i)
    {
        super(s, i);
    }
    public static T[] values()
    {
        T at[];
        int i;
        T at1[];
        System.arraycopy(at = ENUM$VALUES, 0, at1 = new T[i = at.length], 0, i);
        return at1;
    }
 
    public static T valueOf(String s)
    {
        return (T)Enum.valueOf(demo/T, s);
    }
 
    public static final T SPRING;
    public static final T SUMMER;
    public static final T AUTUMN;
    public static final T WINTER;
    private static final T ENUM$VALUES[];
    static
    {
        SPRING = new T("SPRING", 0);
        SUMMER = new T("SUMMER", 1);
        AUTUMN = new T("AUTUMN", 2);
        WINTER = new T("WINTER", 3);
        ENUM$VALUES = (new T[] {
            SPRING, SUMMER, AUTUMN, WINTER
        });
    }
}

public final class T extends Enum,說明該類是繼承了Enum類的,同時final關鍵字告訴咱們,這個類也是不能被繼承的。當咱們使用enmu來定義一個枚舉類型的時候,編譯器會自動幫咱們建立一個final類型的類繼承Enum類。它的屬性和方法都是static類型的,所以是線程安全的。

另外,以前的單例模式都有一個比較大的問題,就是一旦實現了Serializable接口以後,就再也不是單例的了,由於,每次調用 readObject()方法返回的都是一個新建立出來的對象,有一種解決辦法就是使用readResolve()方法來避免此事發生。可是,爲了保證枚舉類型像Java規範中所說的那樣,每個枚舉類型極其定義的枚舉變量在JVM中都是惟一的,在枚舉類型的序列化和反序列化上,Java作了特殊的規定。原文以下:

Enum constants are serialized differently than ordinary serializable or externalizable objects. The serialized form of an enum constant consists solely of its name; field values of the constant are not present in the form. To serialize an enum constant, ObjectOutputStream writes the value returned by the enum constant’s name method. To deserialize an enum constant, ObjectInputStream reads the constant name from the stream; the deserialized constant is then obtained by calling the java.lang.Enum.valueOf method, passing the constant’s enum type along with the received constant name as arguments. Like other serializable or externalizable objects, enum constants can function as the targets of back references appearing subsequently in the serialization stream. The process by which enum constants are serialized cannot be customized: any class-specific writeObject, readObject, readObjectNoData, writeReplace, and readResolve methods defined by enum types are ignored during serialization and deserialization. Similarly, any serialPersistentFields or serialVersionUID field declarations are also ignored–all enum types have a fixedserialVersionUID of 0L. Documenting serializable fields and data for enum types is unnecessary, since there is no variation in the type of data sent.

大概意思就是說,在序列化的時候Java僅僅是將枚舉對象的name屬性輸出到結果中,反序列化的時候則是經過java.lang.Enum的valueOf方法來根據名字查找枚舉對象。同時,編譯器是不容許任何對這種序列化機制的定製的,所以禁用了writeObject、readObject、readObjectNoData、writeReplace和readResolve等方法。 咱們看一下這個valueOf方法:

public static <T extends Enum<T>> T valueOf(Class<T> enumType,String name) {  
            T result = enumType.enumConstantDirectory().get(name);  
            if (result != null)  
                return result;  
            if (name == null)  
                throw new NullPointerException("Name is null");  
            throw new IllegalArgumentException(  
                "No enum const " + enumType +"." + name);  
}  

從代碼中能夠看到,代碼會嘗試從調用enumType這個Class對象的enumConstantDirectory()方法返回的map中獲取名字爲name的枚舉對象,若是不存在就會拋出異常。再進一步跟到enumConstantDirectory()方法,就會發現到最後會以反射的方式調用enumType這個類型的values()靜態方法,也就是上面咱們看到的編譯器爲咱們建立的那個方法,而後用返回結果填充enumType這個Class對象中的enumConstantDirectory屬性。

#筆記內容參考

1.《java併發編程實戰》

2.《java與模式》

3. http://www.hollischuang.com/archives/197

相關文章
相關標籤/搜索