單例模式保證一個類只有一個實例,并向外提供一個全局的訪問入口,單例類需自己實例化唯一的實例。Java 中 java.lang.Runtime
就是典型的單例模式實現(xiàn)例子,現(xiàn)實生活中的例子也比較多,比如:一個學(xué)校有一個校長、一個公司有一個董事長等。
優(yōu)點:因為單例模式只生成一個實例,所以能夠節(jié)約系統(tǒng)資源、減少性能開銷,同時也能避免對共享資源的多重占用。
缺點:同樣因為只有一個實例,導(dǎo)致單例類的職責過重,與單一職責原則沖突,它沒有接口,不能繼承,不利于擴展。
常見的單例模式實現(xiàn)方式大致有五種,分別為:餓漢式、懶漢式、雙重檢測鎖、靜態(tài)內(nèi)部類、枚舉,通常我們需要保證單例模式的線程安全,下面來看一下如何通過這五種方式實現(xiàn)線程安全的單例模式。
餓漢式從字面上我們就能了解個大概:餓了就要馬上吃飯,這種模式在類加載時就初始化實例,缺點是容易產(chǎn)生垃圾對象,因為可能我們還沒有用到實例的時候,實例就已經(jīng)被創(chuàng)建了;優(yōu)點是線程安全,不用加鎖,效率高。示例如下:
public class Singleton { private static Singleton instance = new Singleton(); //將構(gòu)造器設(shè)置為 private,禁止通過 new 實例化 private Singleton() {} public static Singleton getInstance() { return instance; }}
懶漢式就是懶加載,在首次調(diào)用時進行初始化,避免了內(nèi)存被浪費問題,但這種方式在多線程情況下需要通過加鎖(如:synchronized )來保證線程安全,加鎖會影響效率,因此這種方式效率可能會比較低。示例如下:
public class Singleton { private static Singleton instance; private Singleton() {} public static Singleton getInstance() { synchronized(Singleton.class) { if (instance == null) { instance = new Singleton(); } } return instance; }}
這種方式采用雙鎖機制,在多線程情況下既能保證線程安全,同時又能保持較高的性能,當然這種方式實現(xiàn)會相對復(fù)雜了一點。示例如下:
public class Singleton { private volatile static Singleton instance; private Singleton (){} public static Singleton getInstance() { if (instance == null) { synchronized (Singleton.class) { if (instance == null) { instance = new Singleton(); } } } return instance; } }
從示例中我們可以看到,這種方式進行了兩次判斷,第一次是為了避免不要的實例創(chuàng)建,第二次是為了進行同步,避免多線程問題。instance = new Singleton()
創(chuàng)建時在 JVM 中可能會進行指令重排序,在多線程情況下會存在線程安全問題,使用 volatile
修飾 instance
解決該問題。
這種方式和餓漢式一樣,通過 ClassLoader 的機制保證了線程安全,不同之處餓漢式一旦 Singleton 類被裝載了,那么 instance 就會被實例化,而這種方式只有內(nèi)部類(SingletonInner)被主動使用時才會實例化單例對象;該方式可以達到雙重檢測鎖方式的效率,實現(xiàn)相對簡單一點,當然這種方式只適用于靜態(tài)域的情況,雙重檢測鎖方式可在實例域需要延遲初始化時使用。
示例如下:
public class Singleton { private static class SingletonInner { private static final Singleton instance = new Singleton(); } private Singleton() {} public static final Singleton getInstance() { return SingletonInner.instance; }}
默認枚舉實例的創(chuàng)建是線程安全的,它自動支持序列化機制,可避免反射,防止多次實例化,因此在任何情況下都是單例的,它的實現(xiàn)也比較簡單,但由于 JDK1.5 之后才加入 enum
,這種方式在實際工作中用的還是比較少。示例如下:
public enum Singleton { INSTANCE; public void getInstance() {} }