深刻理解Java中單例模式的實現

來源:互聯網
上載者:User

標籤:單例模式-序列化

在之前的學習筆記中已經寫了一篇關於單例模式的幾種不同實現。這篇文章主要是對之前的那篇筆記的補充和加深。
· 在Java語言中使用單例模式能夠帶來的好處:
(1):對於頻繁使用的對象,可以省略建立對象那個所花費的時間,尤其是那些重量級對象的建立,對於重量級對象的建立那可是一筆相當可觀的系統開銷。
(2):由於new操作的次數減少了,進一步產生的益處就是,對系統記憶體的使用頻率也會降低了,那麼這一舉措將會減輕GC壓力,縮短GC停頓時間。
以上的這兩點都為系統效能的最佳化帶來了改善。
單例模式的實現:
簡單可靠型:

public class Singleton {    private **static** Singleton instance = new Singleton();    **private** Singleton(){    }    public **static** Singleton getInstance(){        return instance;    }}

這種單例模式的實現非常簡單,而且十分可靠,執行個體的建立時機是在類載入時由JVM負責建立的。但是這樣會帶這種實現模式唯一存在的不足:我們無法對instance執行個體做延時載入。想象一下如下的情境:
當我們建立的單例類在系統中扮演多種角色時,由於單例類執行個體的建立是交由JVM負責建立的,那麼任何主動使用該類的地方都將觸發JVM載入該單例類,進而單例對象就會被建立,而不管此時我們是否真的需要使用該類的執行個體對象。
改進型(一):

public class Singleton {    private **static** Singleton instance = new Singleton();    **private** Singleton(){    }    public **stati**c Singleton getInstance(){        return instance;    }    //該單例類還擔任別的角色,負責產生一個隨機數    public **static** int generateNumder(){        return (int)Math.random() * 100;    }}

這個時候當我們使用該類的generateNumer()方法時,我們可能便不希望載入該類的執行個體。下面我們將引入消極式載入機制來實現單例模式:
完善型:

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

引入消極式載入機制時一定要考慮多線程環境下系統可能出現的問題,所以關鍵字synchronized和檢查null操作是必不可少的,但是這樣一來系統載入執行個體對象的時耗就變長了。為了最佳化時耗這一瓶頸,我們可以進行如改進:

public class StaticSingleton{    private StaticSingleton(){}    private static class SingletonHolder{        private static StaticSingleton instance = new StaticSingleton();    }    public static StaticSingleton getInstance(){        return SingletonHolder.instance;    }}

在上面的實現了,單例模式採用了內部類來負責維護單例的執行個體對象,當StaticSingleton被載入時,其內部類並不會被初始化(在StaticSingleton類中除了getInstance方法主動使用了內部類SingletonHolder之外,沒有別的地方存在主動使用該內部類,所有不會導致內部類初始化)。同時,由於執行個體的建立交由JVM在類載入時進行建立,所以該種實現天生對多線程就是友好的。這種實現兼具了上面兩種實現的優點。

通常情況下,我們採用上面的三種實現方式之一都能實現單例模式,但是任然存在例外情況,可能導致系統產生多個執行個體,Java中常見的就是反射

單例模式的另一種實現,該種實現也是《Effective Java》中所推薦的,在JDK5.0以後都可以獲得支援,採用枚舉實現:

四、枚舉,《Effective Java》作者推薦使用的方法,優點:不僅能避免多線程同步問題,而且還能防止還原序列化重新建立新的對象 */public enum EnumSingleton {    INSTANCE;}

額外的知識:單例模式在序列化和還原序列化時的使用:
一個刻序列化的單例模式實現Demo:

public class Singleton implements java.io.Serializable {    private String clazzName;    private static Singleton instance = new Singleton();    private Singleton(){        this.name = "Demo";    }    public static Singleton getInstance(){        return instance;    }    public **static** int generateNumder(){        return (int)Math.random() * 100;    }     ***// 阻止產生新的執行個體,總是返回當前對象    private Object readResolve(){        return instance;    }***}

關於 readResolve 方法的一些知識:引用自
http://download.oracle.com/javase/1.5.0/docs/guide/serialization/spec/input.html
For Serializable and Externalizable classes, the readResolve method allows a class to replace/resolve the object read from the stream before it is returned to the caller. By implementing the readResolve method, a class can directly control the types and instances of its own instances being deserialized. The method is defined as follows:
ANY-ACCESS-MODIFIER Object readResolve()
throws ObjectStreamException;
The readResolve method is called when ObjectInputStream has read an object from the stream and is preparing to return it to the caller. ObjectInputStream checks whether the class of the object defines the readResolve method. If the method is defined, the readResolve method is called to allow the object in the stream to designate the object to be returned. The object returned should be of a type that is compatible with all uses. If it is not compatible, a ClassCastException will be thrown when the type mismatch is discovered.
For example, a Symbol class could be created for which only a single instance of each symbol binding existed within a virtual machine. The readResolve method would be implemented to determine if that symbol was already defined and substitute the preexisting equivalent Symbol object to maintain the identity constraint. In this way the uniqueness of Symbol objects can be maintained across serialization.

Note - The readResolve method is not invoked on the object until the object is fully constructed, so any references to this object in its object graph will not be updated to the new object nominated by readResolve. However, during the serialization of an object with the writeReplace method, all references to the original object in the replacement object’s object graph are replaced with references to the replacement object. Therefore in cases where an object being serialized nominates a replacement object whose object graph has a reference to the original object, deserialization will result in an incorrect graph of objects. Furthermore, if the reference types of the object being read (nominated by writeReplace) and the original object are not compatible, the construction of the object graph will raise a ClassCastException.
序列化對象通過流傳給調用者,當調用者從ObjectInputStream流中讀取序列化對象時,序列化對象返回給調用者之前會先查看是否已經實現這個方法,如果實現那麼就返回這個對象的傳回值,如果傳回值和調用者期望獲得的類類型轉換不匹配,那麼就會報ClassCastException錯誤。
但是,前提是,在調用者通過流擷取序列化對象時,序列化對象必須已經fully constructed,不然序列化對象不會找這個方法。

方法readResolve會在ObjectInputStream已經讀取一個對象並在準備返回前調用。ObjectInputStream 會檢查對象的class是否定義了readResolve方法。如果定義了,將由readResolve方法指定返回的對象。返回對象的類型一定要是相容的,否則會拋出ClassCastException 。

深刻理解Java中單例模式的實現

相關文章

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.