您好,登錄后才能下訂單哦!
這篇文章主要為大家展示了“java中單例模式有幾種寫法”,內容簡而易懂,條理清晰,希望能夠幫助大家解決疑惑,下面讓小編帶領大家一起研究并學習一下“java中單例模式有幾種寫法”這篇文章吧。
1、餓漢式
餓漢式是單例模式設計中比較經典的實現方式。實現代碼如下:
//final不允許被繼承 public final class SingleTonEhangshi { //實例變量 private byte[] data = new byte[1024]; //在定義實例對象時直接初始化 private static SingleTonEhangshi instance = new SingleTonEhangshi(); //私有化構造函數,不允許外部NEW private SingleTonEhangshi() { } public static SingleTonEhangshi getInstance() { return instance; } }
餓漢式的實現關鍵在于instance作為類變量直接得到了初始化,如果我們主動使用SingleToEhangshi類,那么instance實例將會直接完成創建,包括其中的實例變量也都會得到初始化。
instance作為類變量,在類初始化的過程中會被收集進<clinit>()方法中,而該方法是可以100%地保證同步,也就是說instance在多線程的情況下不可能被初始化兩次。但是由于instance被ClassLoader加載后很長一段時間才被使用的話,那就會意味著instance實例所開辟的堆內存會駐留很長的時間。
總體說來,如果一個類中的成員變量比較少,且占用的內存資源也不多,用餓漢式的方式實現單例模式也未嘗不可,只是其無法進行懶加載。
2、懶漢式
所謂懶漢式就是在使用類實例的時候再去創建,也就是說用到的時候我再創建,這樣就可以避免類在初始化的時候提前創建過早地占用內存空間。實現代碼如下:
//final不允許被繼承 public final class SingleTonLhangshi { //實例變量 private byte[] data = new byte[1024]; //定義實例,但是不直接初始化 private static SingleTonLhangshi instance = null; //私有化構造函數,不允許外部NEW private SingleTonLhangshi() { } public static SingleTonLhangshi getInstance() { if (null == instance) { instance = new SingleTonLhangshi(); } return instance; } }
類變量instance=null,因此當類被初始化的時候instance并不會立刻被實例化,而是在getInstance()方法被調用時判斷instance實例是否被實例化,如果沒有實例化在調用私有構造方法進行實例化操作。
懶漢式寫法在多線程環境下,會存在同一時間多個線程同時看到null==instance的情況,從而導致instance會被實例化多次,從而無法保證單例的唯一性。
3、懶漢式+同步方法
懶漢式的單例實現方式可以保證實例的懶加載,但是卻無法保證實例的唯一性。在多線程環境下由于instance為共享數據,當多個線程訪問使用時,需要保證數據的同步性,所以如果需要保證懶漢式實例的唯一性,我們可以通過同步的方式來實現。代碼如下:
/final不允許被繼承 public final class SingleTonLhangshiSync { //實例變量 private byte[] data = new byte[1024]; //定義實例,但是不直接初始化 private static SingleTonLhangshiSync instance = null; //私有化構造函數,不允許外部NEW private SingleTonLhangshiSync() { } //向getInstance方法加入同步控制,每次只能有一個線程能夠進入 public static synchronized SingleTonLhangshiSync getInstance() { if (null == instance) { instance = new SingleTonLhangshiSync(); } return instance; } }
采用懶漢式+數據同步的方法既滿足了懶加載又能夠100%保證instance實例的唯一性。但是,synchronized關鍵字的排它性會導致getInstance()方法同一時刻只能被一個線程訪問,性能會比較低下。
4、Double-Check
Double-Check是一種比較聰明的設計方式,它提供了一種高效的數據同步策略,那就是首次初始化的時候加鎖,之后則允許多個線程同時進行getInstance()方法的調用來獲得類的實例。代碼如下:
//final不允許被繼承 public final class SingletonDoubleCheck { //實例變量 private byte[] data = new byte[1024]; //定義實例,但是不直接初始化 private static SingletonDoubleCheck instance = null; Connection con; Socket socket; //私有化構造函數,不允許外部NEW private SingletonDoubleCheck() { this.con = con;//初始化 this.socket = socket;//初始化 } public static SingletonDoubleCheck getInstance() { //當instance為null時,進入同步代碼塊,同時該判斷避免了每次都需要進入同步代碼塊,可以提高效率 if (null == instance) { //只有一個線程能夠獲得SingletonDoubleCheck.class關聯的monitor synchronized (SingletonDoubleCheck.class) { //判斷如果instance為null則創建 if (null == instance) { instance = new SingletonDoubleCheck(); } } } return instance; } }
當兩個線程發現null==instance成立時,只有一個線程有資格進入同步代碼塊,完成對instance的初始化,隨后的線程發現null==instance不成立則無須進行任何操作,以后對getInstance的訪問就不會再需要進行數據同步了。
此種方式看起來是既滿足了懶加載,又保證了instance實例的唯一性,并且還提供了比較高效的數據同步策略,可以允許多個線程同時對getInstance進行訪問。但是這種方式在多線程的情況下,可能會引起空指針異常,這是因為如果在如上代碼的構造方法中還存在初始化其他資源的情況的話,由于JVM運行時存在指令重排的情況,這些資源在實例化時順序并無前后關系的約束,那么在這種情況下,就極有可能是instance最先被實例化,而con和socket并未完成實例化,而未完成實例化的實例在調用其方法時將會拋出空指針異常。
5、Volatile+Double-Check
為了解決Double-Check指令重排導致的空指針問題,可以用volatile關鍵字防止這種重排序的發生。因此代碼只需要稍作修改就能滿足多線程下的單例、懶加載以及實例的高效性了。代碼如下:
//final不允許被繼承 public final class SingletonDoubleCheck { //實例變量 private byte[] data = new byte[1024]; //定義實例,但是不直接初始化 private static volatile SingletonDoubleCheck instance = null; Connection con; Socket socket; //私有化構造函數,不允許外部NEW private SingletonDoubleCheck() { this.con = con;//初始化 this.socket = socket;//初始化 } public static SingletonDoubleCheck getInstance() { //當instance為null時,進入同步代碼塊,同時該判斷避免了每次都需要進入同步代碼塊,可以提高效率 if (null == instance) { //只有一個線程能夠獲得SingletonDoubleCheck.class關聯的monitor synchronized (SingletonDoubleCheck.class) { //判斷如果instance為null則創建 if (null == instance) { instance = new SingletonDoubleCheck(); } } } return instance; } }
6、Holder方式
Holder方式完全借助了類加載的特點。代碼如下:
//不允許被繼承 public final class SingletonHolder { //實例變量 private byte[] data = new byte[1024]; private SingletonHolder() { } //在靜態內部類中持有單例類的實例,并且可直接被初始化 private static class Holder { private static SingletonHolder instance = new SingletonHolder(); } //調用getInstance方法,事實上是獲得Holder的instance靜態屬性 public static SingletonHolder getInstance() { return Holder.instance; } }
在單例類中并沒有instance的靜態成員,而是將其放到了靜態內部類Holder之中,因此單例類在初始化的過程中并不會創建SingletonHolder的實例,內部類Holder中定義了SingletonHolder的靜態變量,并且直接進行了實例化,只有當Holder被主動引用的時候才會創建SingletonHolder的實例。
SingletonHolder實例的創建過程在Java程序編譯時期收集至<clinit>()方法中,該方法又是同步方法,可以保證內存的可見性、JVM指令的順序性和原子性。Holder方式的單例模式設計是最好的設計之一,也是目前使用比較廣的設計。
7、枚舉方式
枚舉方式在很多開源框架中也應用得比較廣泛,枚舉類型不允許被繼承,同樣是線程安全的,并且只能被實例化一次,但是枚舉類型不能夠實現懶加載。用枚舉類型,實現單例模式的代碼如下:
public class SingletonEnum { //實例變量 private byte[] data = new byte[1024]; private SingletonEnum() { } //使用枚舉充當Holder private enum EnumHolder { INSTANCE; private SingletonEnum instance; EnumHolder() { this.instance = new SingletonEnum(); } private SingletonEnum getInstance() { return instance; } } public static SingletonEnum getInstance() { return EnumHolder.INSTANCE.getInstance(); } }
以上是“java中單例模式有幾種寫法”這篇文章的所有內容,感謝各位的閱讀!相信大家都有了一定的了解,希望分享的內容對大家有所幫助,如果還想學習更多知識,歡迎關注億速云行業資訊頻道!
免責聲明:本站發布的內容(圖片、視頻和文字)以原創、轉載和分享為主,文章觀點不代表本網站立場,如果涉及侵權請聯系站長郵箱:is@yisu.com進行舉報,并提供相關證據,一經查實,將立刻刪除涉嫌侵權內容。