一区二区三区在线-一区二区三区亚洲视频-一区二区三区亚洲-一区二区三区午夜-一区二区三区四区在线视频-一区二区三区四区在线免费观看

服務(wù)器之家:專注于服務(wù)器技術(shù)及軟件下載分享
分類導(dǎo)航

PHP教程|ASP.NET教程|Java教程|ASP教程|編程技術(shù)|正則表達(dá)式|C/C++|IOS|C#|Swift|Android|VB|R語言|JavaScript|易語言|vb.net|

服務(wù)器之家 - 編程語言 - Java教程 - 深入理解Java中沒那么簡單的單例模式

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

2020-07-28 14:23CSDNviclee108 Java教程

這篇文章主要給大家詳細(xì)介紹了Java單例模式,關(guān)于Java中的單例模式并非看起來那么簡單的,為什么要這么說呢?下面通過這篇文章來一起看看吧,有需要的朋友們可以參考借鑒。

前言

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

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

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

1、餓漢模式

?
1
2
3
4
5
6
7
public class Singleton{
 private static Singleton instance = new Singleton();
 private Singleton(){}
 public static Singleton newInstance(){
 return instance;
 }
}

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

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

2、懶漢模式

?
1
2
3
4
5
6
7
8
9
10
public class Singleton{
 private static Singleton instance = null;
 private Singleton(){}
 public static Singleton newInstance(){
 if(null == instance){
  instance = new Singleton();
 }
 return instance;
 }
}

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

?
1
2
3
4
5
6
7
8
9
10
public class Singleton{
 private static Singleton instance = null;
 private Singleton(){}
 public static synchronized Singleton newInstance(){
 if(null == instance){
  instance = new Singleton();
 }
 return instance;
 }
}

3、雙重校驗鎖

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

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
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為空的判斷。由于單例對象只需要創(chuàng)建一次,如果后面再次調(diào)用getInstance()只需要直接返回單例對象。因此,大部分情況下,調(diào)用getInstance()都不會執(zhí)行到同步代碼塊,從而提高了程序性能。不過還需要考慮一種情況,假如兩個線程A、B,A執(zhí)行了if (instance == null)語句,它會認(rèn)為單例對象沒有創(chuàng)建,此時線程切到B也執(zhí)行了同樣的語句,B也認(rèn)為單例對象沒有創(chuàng)建,然后兩個線程依次執(zhí)行同步代碼塊,并分別創(chuàng)建了一個單例對象。為了解決這個問題,還需要在同步代碼塊中增加if (instance == null)語句,也就是上面看到的代碼2。

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

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

以上就是雙重校驗鎖會失效的原因,不過還好在JDK1.5及之后版本增加了volatile關(guān)鍵字。volatile的一個語義是禁止指令重排序優(yōu)化,也就保證了instance變量被賦值的時候?qū)ο笠呀?jīng)是初始化過的,從而避免了上面說到的問題。

代碼如下:

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
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、靜態(tài)內(nèi)部類

除了上面的三種方式,還有另外一種實(shí)現(xiàn)單例的方式,通過靜態(tài)內(nèi)部類來實(shí)現(xiàn)。首先看一下它的實(shí)現(xiàn)代碼:

?
1
2
3
4
5
6
7
8
9
public class Singleton{
 private static class SingletonHolder{
 public static Singleton instance = new Singleton();
 }
 private Singleton(){}
 public static Singleton newInstance(){
 return SingletonHolder.instance;
 }
}

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

5、枚舉

再來看本文要介紹的最后一種實(shí)現(xiàn)方式:枚舉。

?
1
2
3
4
public enum Singleton{
 instance;
 public void whateverMethod(){}
}

面提到的四種實(shí)現(xiàn)單例的方式都有共同的缺點(diǎn):

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

2)可以使用反射強(qiáng)行調(diào)用私有構(gòu)造器(如果要避免這種情況,可以修改構(gòu)造器,讓它在創(chuàng)建第二個實(shí)例的時候拋異常)。

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

總結(jié)

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

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

原文鏈接:http://blog.csdn.net/goodlixueyong/article/details/51935526

延伸 · 閱讀

精彩推薦
主站蜘蛛池模板: 精品牛牛影视久久精品 | 国色天香视频资源网 | 毛片免费观看 | 视频二区 素人 制服 国产 | 久久99亚洲热最新地址获取 | swag最新正在播放 | 国产一级毛片国语版 | 好男人免费高清在线观看2019 | 国产成人成人一区二区 | 好涨好爽好大视频免费 | 国产在线乱子伦一区二区 | 国产成人精品免费视频大全五级 | 成人精品第一区二区三区 | 爽好紧别夹宝贝叫大声点护士 | 成人影院vs一区二区 | 天天射寡妇射 | 五月激情丁香婷婷综合第九 | 第一次破女视频国产一级 | 久久久精品成人免费看 | 久久日本片精品AAAAA国产 | 玩两个少妇女邻居 | 天天操免费视频 | 2021国产精品成人免费视频 | 校花被扒开尿口折磨憋尿 | 99热精品在线播放 | 亚洲午夜天堂 | 精品久久久噜噜噜久久7 | 女性性色生活片免费观看 | 久久婷婷五月综合色丁香 | 国产欧美视频一区二区三区 | 热久久免费| 免费yjsp妖精com| 曹逼网站 | 婚前试爱免费观看 | 美女的隐私视频免费看软件 | 日本久久免费大片 | 日韩欧美综合在线二区三区 | 国产成人精品视频一区二区不卡 | 青青操在线播放 | 欧美人禽杂交狂配无删完整 | 成人久久18免费网站 |