国产探花免费观看_亚洲丰满少妇自慰呻吟_97日韩有码在线_资源在线日韩欧美_一区二区精品毛片,辰东完美世界有声小说,欢乐颂第一季,yy玄幻小说排行榜完本

首頁 > 編程 > Java > 正文

深入理解Java中沒那么簡單的單例模式

2019-11-26 13:14:03
字體:
來源:轉載
供稿:網友

前言

大家都知道關于Java中單例(Singleton)模式是一種廣泛使用的設計模式。單例模式的主要作用是保證在Java程序中,某個類只有一個實例存在。一些管理器和控制器常被設計成單例模式。

單例模式有很多好處,它能夠避免實例對象的重復創建,不僅可以減少每次創建對象的時間開銷,還可以節約內存空間;能夠避免由于操作多個實例導致的邏輯錯誤。如果一個對象有可能貫穿整個應用程序,而且起到了全局統一管理控制的作用,那么單例模式也許是一個值得考慮的選擇。

單例模式有很多種寫法,大部分寫法都或多或少有一些不足。下面將分別對這幾種寫法進行介紹。

1、餓漢模式

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

從代碼中我們看到,類的構造函數定義為private的,保證其他類不能實例化此類,然后提供了一個靜態實例并返回給調用者。餓漢模式是最簡單的一種實現方式,餓漢模式在類加載的時候就對實例進行創建,實例在整個程序周期都存在。它的好處是只在類加載的時候創建一次實例,不會存在多個線程創建多個實例的情況,避免了多線程同步的問題。它的缺點也很明顯,即使這個單例沒有用到也會被創建,而且在類加載之后就被創建,內存就被浪費了。

這種實現方式適合單例占用內存比較小,在初始化時就會被用到的情況。但是,如果單例占用的內存比較大,或單例只是在某個特定場景下才會用到,使用餓漢模式就不合適了,這時候就需要用到懶漢模式進行延遲加載。

2、懶漢模式

public class Singleton{  private static Singleton instance = null;  private Singleton(){}  public static Singleton newInstance(){  if(null == instance){   instance = new Singleton();  }  return instance;  } } 

懶漢模式中單例是在需要的時候才去創建的,如果單例已經創建,再次調用獲取接口將不會重新創建新的對象,而是直接返回之前創建的對象。如果某個單例使用的次數少,并且創建單例消耗的資源較多,那么就需要實現單例的按需創建,這個時候使用懶漢模式就是一個不錯的選擇。但是這里的懶漢模式并沒有考慮線程安全問題,在多個線程可能會并發調用它的getInstance()方法,導致創建多個實例,因此需要加鎖解決線程同步問題,實現如下。

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

3、雙重校驗鎖

加鎖的懶漢模式看起來即解決了線程并發問題,又實現了延遲加載,然而它存在著性能問題,依然不夠完美。synchronized修飾的同步方法比一般方法要慢很多,如果多次調用getInstance() ,累積的性能損耗就比較大了。因此就有了雙重校驗鎖,先看下它的實現代碼。

public class Singleton {  private static Singleton instance = null;  private Singleton(){}  public static Singleton getInstance() {  if (instance == null) {   synchronized (Singleton.class) {   if (instance == null) {//2    instance = new Singleton();   }   }  }  return instance;  } } 

可以看到上面在同步代碼塊外多了一層instance為空的判斷。由于單例對象只需要創建一次,如果后面再次調用getInstance()只需要直接返回單例對象。因此,大部分情況下,調用getInstance()都不會執行到同步代碼塊,從而提高了程序性能。不過還需要考慮一種情況,假如兩個線程A、B,A執行了if (instance == null)語句,它會認為單例對象沒有創建,此時線程切到B也執行了同樣的語句,B也認為單例對象沒有創建,然后兩個線程依次執行同步代碼塊,并分別創建了一個單例對象。為了解決這個問題,還需要在同步代碼塊中增加if (instance == null)語句,也就是上面看到的代碼2。

我們看到雙重校驗鎖即實現了延遲加載,又解決了線程并發問題,同時還解決了執行效率問題,是否真的就萬無一失了呢?
這里要提到Java中的指令重排優化。所謂指令重排優化是指在不改變原語義的情況下,通過調整指令的執行順序讓程序運行的更快。JVM中并沒有規定編譯器優化相關的內容,也就是說JVM可以自由的進行指令重排序的優化。

這個問題的關鍵就在于由于指令重排優化的存在,導致初始化Singleton和將對象地址賦給instance字段的順序是不確定的。在某個線程創建單例對象時,在構造方法被調用之前,就為該對象分配了內存空間并將對象的字段設置為默認值。此時就可以將分配的內存地址賦值給instance字段了,然而該對象可能還沒有初始化。若緊接著另外一個線程來調用getInstance,取到的就是狀態不正確的對象,程序就會出錯。

以上就是雙重校驗鎖會失效的原因,不過還好在JDK1.5及之后版本增加了volatile關鍵字。volatile的一個語義是禁止指令重排序優化,也就保證了instance變量被賦值的時候對象已經是初始化過的,從而避免了上面說到的問題。

代碼如下:

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

4、靜態內部類

除了上面的三種方式,還有另外一種實現單例的方式,通過靜態內部類來實現。首先看一下它的實現代碼:

public class Singleton{  private static class SingletonHolder{  public static Singleton instance = new Singleton();  }  private Singleton(){}  public static Singleton newInstance(){  return SingletonHolder.instance;  } } 

這種方式同樣利用了類加載機制來保證只創建一個instance實例。它與餓漢模式一樣,也是利用了類加載機制,因此不存在多線程并發的問題。不一樣的是,它是在內部類里面去創建對象實例。這樣的話,只要應用中不使用內部類,JVM就不會去加載這個單例類,也就不會創建單例對象,從而實現懶漢式的延遲加載。也就是說這種方式可以同時保證延遲加載和線程安全。

5、枚舉

再來看本文要介紹的最后一種實現方式:枚舉。

public enum Singleton{  instance;  public void whateverMethod(){} } 

面提到的四種實現單例的方式都有共同的缺點:

1)需要額外的工作來實現序列化,否則每次反序列化一個序列化的對象時都會創建一個新的實例。

2)可以使用反射強行調用私有構造器(如果要避免這種情況,可以修改構造器,讓它在創建第二個實例的時候拋異常)。

       而枚舉類很好的解決了這兩個問題,使用枚舉除了線程安全和防止反射調用構造器之外,還提供了自動序列化機制,防止反序列化的時候創建新的對象。因此,《Effective Java》作者推薦使用的方法。不過,在實際工作中,很少看見有人這么寫。

總結

本文總結了五種Java中實現單例的方法,其中前兩種都不夠完美,雙重校驗鎖和靜態內部類的方式可以解決大部分問題,平時工作中使用的最多的也是這兩種方式。枚舉方式雖然很完美的解決了各種問題,但是這種寫法多少讓人感覺有些生疏。個人的建議是,在沒有特殊需求的情況下,使用第三種和第四種方式實現單例模式。

以上就是這篇文章的全部內容了,希望本文的內容對大家的學習或者工作能帶來一定的幫助,如果有疑問大家可以留言交流。

發表評論 共有條評論
用戶名: 密碼:
驗證碼: 匿名發表
主站蜘蛛池模板: 阳泉市| 龙游县| 怀化市| 商南县| 盘山县| 凤阳县| 扶余县| 鸡泽县| 临潭县| 望江县| 水城县| 湖北省| 抚松县| 尖扎县| 文水县| 诸城市| 江油市| 玉龙| 克拉玛依市| 阿拉善盟| 滨海县| 交口县| 奇台县| 科尔| 营山县| 长葛市| 繁峙县| 酒泉市| 开远市| 玉山县| 凌海市| 方城县| 报价| 黄浦区| 罗山县| 澄城县| 独山县| 彭山县| 南城县| 兰溪市| 陕西省|