單例模式學(xué)習(xí)

Java中單例(Singleton)模式是一種廣泛使用的設(shè)計(jì)模式饮醇。單例模式的主要作用是保證在Java程序中,某個(gè)類只有一個(gè)實(shí)例存在蝶防。


一些管理器和控制器常被設(shè)計(jì)成單例模式窄锅。


單例模式好處:


它能夠避免實(shí)例對(duì)象的重復(fù)創(chuàng)建,不僅可以減少每次創(chuàng)建對(duì)象的時(shí)間開(kāi)銷木柬,還可以節(jié)約內(nèi)存空間皆串;

能夠避免由于操作多個(gè)實(shí)例導(dǎo)致的邏輯錯(cuò)誤。

如果一個(gè)對(duì)象有可能貫穿整個(gè)應(yīng)用程序眉枕,而且起到了全局統(tǒng)一管理控制的作用恶复,那么單例模式也許是一個(gè)值得考慮的選擇。

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


1姥宝、餓漢模式

public class Singleton{

? ? private static Singleton instance = new Singleton();

? ? private Singleton(){}

? ? public static Singleton newInstance(){

? ? ? ? return instance;

? ? }

}

?


從代碼中我們看到翅萤,類的構(gòu)造函數(shù)定義為private的,保證其他類不能實(shí)例化此類腊满,然后提供了一個(gè)靜態(tài)實(shí)例并返回給調(diào)用者套么。餓漢模式是最簡(jiǎn)單的一種實(shí)現(xiàn)方式培己,餓漢模式在類加載的時(shí)候就對(duì)實(shí)例進(jìn)行創(chuàng)建,實(shí)例在整個(gè)程序周期都存在胚泌。

它的好處是只在類加載的時(shí)候創(chuàng)建一次實(shí)例省咨,不會(huì)存在多個(gè)線程創(chuàng)建多個(gè)實(shí)例的情況,避免了多線程同步的問(wèn)題诸迟。

它的缺點(diǎn)也很明顯茸炒,即使這個(gè)單例沒(méi)有用到也會(huì)被創(chuàng)建,而且在類加載之后就被創(chuàng)建阵苇,內(nèi)存就被浪費(fèi)了壁公。

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


2掀亥、懶漢模式

public class Singleton{

? ? private static Singleton instance = null;

? ? private Singleton(){}

? ? public static Singleton newInstance(){

? ? ? ? if(null == instance){

? ? ? ? ? ? instance = new Singleton();

? ? ? ? }

? ? ? ? return instance;

? ? }

}


好處:懶漢模式中單例是在需要的時(shí)候才去創(chuàng)建的撞反,如果單例已經(jīng)創(chuàng)建,再次調(diào)用獲取接口將不會(huì)重新創(chuàng)建新的對(duì)象搪花,而是直接返回之前創(chuàng)建的對(duì)象遏片。

適用于:如果某個(gè)單例使用的次數(shù)少,并且創(chuàng)建單例消耗的資源較多撮竿,那么就需要實(shí)現(xiàn)單例的按需創(chuàng)建吮便,這個(gè)時(shí)候使用懶漢模式就是一個(gè)不錯(cuò)的選擇。

缺點(diǎn):但是這里的懶漢模式并沒(méi)有考慮線程安全問(wèn)題幢踏,在多個(gè)線程可能會(huì)并發(fā)調(diào)用它的getInstance()方法髓需,導(dǎo)致創(chuàng)建多個(gè)實(shí)例,因此需要加鎖解決線程同步問(wèn)題房蝉,實(shí)現(xiàn)如下:

public class Singleton{

? ? private static Singleton instance = null;

? ? private Singleton(){}

? ? public static synchronized Singleton newInstance(){

? ? ? ? if(null == instance){? // Single Checked

? ? ? ? ? ? instance = new Singleton();

? ? ? ? }

? ? ? ? return instance;

? ? }

}


3僚匆、雙重校驗(yàn)鎖【推薦】

這個(gè)問(wèn)題在Java面試中經(jīng)常被問(wèn)到,但是面試官對(duì)回答此問(wèn)題的滿意度僅為50%搭幻。

一半的人寫不出雙檢鎖白热,還有一半的人說(shuō)不出它的隱患和Java1.5是如何對(duì)它修正的。

它其實(shí)是一個(gè)用來(lái)創(chuàng)建線程安全的單例的老方法粗卜,當(dāng)單例實(shí)例第一次被創(chuàng)建時(shí)它試圖用單個(gè)鎖進(jìn)行性能優(yōu)化,

但是由于太過(guò)于復(fù)雜在JDK1.4中它是失敗的纳击,我個(gè)人也不喜歡它续扔。無(wú)論如何攻臀,即便你也不喜歡它但是還是要了解一下,因?yàn)樗?jīng)常被問(wèn)到纱昧。


加鎖的懶漢模式看起來(lái)即解決了線程并發(fā)問(wèn)題刨啸,又實(shí)現(xiàn)了延遲加載,然而它存在著性能問(wèn)題识脆,依然不夠完美设联。

synchronized修飾的同步方法比一般方法要慢很多,如果多次調(diào)用getInstance()灼捂,累積的性能損耗就比較大了离例。

因此就有了雙重校驗(yàn)鎖,先看下它的實(shí)現(xiàn)代碼悉稠。

public class Singleton {

? ? private static Singleton instance = null;

? ? private Singleton(){}

? ? public static Singleton getInstance() {

? ? ? ? if (instance == null) {? // Single Checked

? ? ? ? ? ? synchronized (Singleton.class) {

? ? ? ? ? ? ? ? if (instance == null) { // Double checked

? ? ? ? ? ? ? ? ? ? instance = new Singleton();

? ? ? ? ? ? ? ? }

? ? ? ? ? ? }

? ? ? ? }

? ? ? ? return instance;

? ? }

}


可以看到上面在同步代碼塊外多了一層instance為空的判斷宫蛆。由于單例對(duì)象只需要?jiǎng)?chuàng)建一次,如果后面再次調(diào)用getInstance()只需要直接返回單例對(duì)象的猛。

因此耀盗,大部分情況下,調(diào)用getInstance()都不會(huì)執(zhí)行到同步代碼塊卦尊,從而提高了程序性能叛拷。

不過(guò)還需要考慮一種情況,假如兩個(gè)線程A岂却、B忿薇,A執(zhí)行了if (instance == null)語(yǔ)句,它會(huì)認(rèn)為單例對(duì)象沒(méi)有創(chuàng)建淌友,此時(shí)線程切到B也執(zhí)行了同樣的語(yǔ)句煌恢,B也認(rèn)為單例對(duì)象沒(méi)有創(chuàng)建,然后兩個(gè)線程依次執(zhí)行同步代碼塊震庭,并分別創(chuàng)建了一個(gè)單例對(duì)象瑰抵。為了解決這個(gè)問(wèn)題,還需要在同步代碼塊中增加if (instance == null)語(yǔ)句器联,也就是上面看到的代碼中的校驗(yàn)2二汛。

雙檢鎖隱患:

我們看到雙重校驗(yàn)鎖即實(shí)現(xiàn)了延遲加載,又解決了線程并發(fā)問(wèn)題拨拓,同時(shí)還解決了執(zhí)行效率問(wèn)題肴颊,是否真的就萬(wàn)無(wú)一失了呢?



這里要提到Java中的指令重排優(yōu)化渣磷。所謂指令重排優(yōu)化是指在不改變?cè)Z(yǔ)義的情況下婿着,通過(guò)調(diào)整指令的執(zhí)行順序讓程序運(yùn)行的更快。

JVM中并沒(méi)有規(guī)定編譯器優(yōu)化相關(guān)的內(nèi)容,也就是說(shuō)JVM可以自由的進(jìn)行指令重排序的優(yōu)化竟宋。

這個(gè)問(wèn)題的關(guān)鍵就在于由于指令重排優(yōu)化的存在提完,導(dǎo)致初始化Singleton和將對(duì)象地址賦給instance字段的順序是不確定的。

在某個(gè)線程創(chuàng)建單例對(duì)象時(shí)丘侠,在構(gòu)造方法被調(diào)用之前徒欣,就為該對(duì)象分配了內(nèi)存空間并將對(duì)象的字段設(shè)置為默認(rèn)值。

此時(shí)就可以將分配的內(nèi)存地址賦值給instance字段了蜗字,然而該對(duì)象可能還沒(méi)有初始化打肝。若緊接著另外一個(gè)線程來(lái)調(diào)用getInstance,取到的就是狀態(tài)不正確的對(duì)象挪捕,程序就會(huì)出錯(cuò)粗梭。


JDK5的修正:以上就是雙重校驗(yàn)鎖會(huì)失效的原因,不過(guò)還好在JDK1.5及之后版本增加了volatile關(guān)鍵字担神。

volatile的一個(gè)語(yǔ)義是禁止指令重排序優(yōu)化楼吃,也就保證了instance變量被賦值的時(shí)候?qū)ο笠呀?jīng)是初始化過(guò)的,從而避免了上面說(shuō)到的問(wèn)題妄讯。

Java中的volatile 變量是什么孩锡?

理解volatile關(guān)鍵字的作用的前提是要理解Java內(nèi)存模型,volatile關(guān)鍵字的作用主要有兩個(gè):

(1)多線程主要圍繞可見(jiàn)性和原子性兩個(gè)特性而展開(kāi)亥贸,使用volatile關(guān)鍵字修飾的變量躬窜,保證了其在多線程之間的可見(jiàn)性,

即每次讀取到volatile變量炕置,一定是最新的數(shù)據(jù)

(2)代碼底層執(zhí)行不像我們看到的高級(jí)語(yǔ)言—-Java程序這么簡(jiǎn)單荣挨,

它的執(zhí)行是Java代碼–>字節(jié)碼–>根據(jù)字節(jié)碼執(zhí)行對(duì)應(yīng)的C/C++代碼–>C/C++代碼被編譯成匯編語(yǔ)言–>和硬件電路交互,

現(xiàn)實(shí)中朴摊,為了獲取更好的性能JVM可能會(huì)對(duì)指令進(jìn)行重排序默垄,多線程下可能會(huì)出現(xiàn)一些意想不到的問(wèn)題。

使用volatile則會(huì)對(duì)禁止語(yǔ)義重排序甚纲,當(dāng)然這也一定程度上降低了代碼執(zhí)行效率

從實(shí)踐角度而言口锭,volatile的一個(gè)重要作用就是和CAS結(jié)合,保證了原子性介杆,

詳細(xì)的可以參見(jiàn)java.util.concurrent.atomic包下的類鹃操,比如AtomicInteger。

CAS(Compare and swap)比較和替換是設(shè)計(jì)并發(fā)算法時(shí)用到的一種技術(shù)春哨。

簡(jiǎn)單來(lái)說(shuō)荆隘,比較和替換是使用一個(gè)期望值和一個(gè)變量的當(dāng)前值進(jìn)行比較,如果當(dāng)前變量的值與我們期望的值相等赴背,就使用一個(gè)新值替換當(dāng)前變量的值椰拒。

volatile是一個(gè)特殊的修飾符晶渠,只有成員變量才能使用它。

在Java并發(fā)程序缺少同步類的情況下耸三,多線程對(duì)成員變量的操作對(duì)其它線程是透明的乱陡。

volatile變量可以保證下一個(gè)讀取操作會(huì)在前一個(gè)寫操作之后發(fā)生。

來(lái)源: http://blog.csdn.net/fly910905/article/details/79283557


代碼如下:

public class Singleton {

? ? private static volatile Singleton instance = null;

? ? private Singleton(){}

? ? public static Singleton getInstance() {

? ? ? ? if (instance == null) { // Single Checked

? ? ? ? ? ? synchronized (Singleton.class) {

? ? ? ? ? ? ? ? if (instance == null) { // Double checked

? ? ? ? ? ? ? ? ? ? instance = new Singleton();

? ? ? ? ? ? ? ? }

? ? ? ? ? ? }

? ? ? ? }

? ? ? ? return instance;

? ? }

}

4仪壮、靜態(tài)內(nèi)部類【推薦】


除了上面的三種方式,還有另外一種實(shí)現(xiàn)單例的方式胳徽,通過(guò)靜態(tài)內(nèi)部類來(lái)實(shí)現(xiàn)积锅。

首先看一下它的實(shí)現(xiàn)代碼:

public class Singleton{

? ? private static class SingletonHolder{

? ? ? ? public static Singleton instance = new Singleton();

? ? }

? ? private Singleton(){}

? ? public static Singleton newInstance(){

? ? ? ? return SingletonHolder.instance;

? ? }

}


這種方式同樣利用了類加載機(jī)制來(lái)保證只創(chuàng)建一個(gè)instance實(shí)例。它與餓漢模式一樣养盗,也是利用了類加載機(jī)制缚陷,因此不存在多線程并發(fā)的問(wèn)題。

不一樣的是往核,它是在內(nèi)部類里面去創(chuàng)建對(duì)象實(shí)例箫爷。

這樣的話,只要應(yīng)用中不使用內(nèi)部類聂儒,JVM就不會(huì)去加載這個(gè)單例類虎锚,也就不會(huì)創(chuàng)建單例對(duì)象,從而實(shí)現(xiàn)懶漢式的延遲加載衩婚。也就是說(shuō)這種方式可以同時(shí)保證延遲加載和線程安全窜护。

5、枚舉

class Resource{

}

public enum SomeThing {

? ? INSTANCE;

? ? private Resource instance;

? ? private SomeThing() {

? ? ? ? instance = new Resource();

? ? }

? ? public Resource getInstance() {

? ? ? ? return instance;

? ? }

}

上面的類Resource是我們要應(yīng)用單例模式的資源非春,具體可以表現(xiàn)為網(wǎng)絡(luò)連接柱徙,數(shù)據(jù)庫(kù)連接,線程池等等奇昙。

獲取資源的方式很簡(jiǎn)單护侮,只要 SomeThing.INSTANCE.getInstance() 即可獲得所要實(shí)例。

下面我們來(lái)看看單例是如何被保證的:

首先储耐,在枚舉中我們明確了構(gòu)造方法限制為私有羊初,在我們?cè)L問(wèn)枚舉實(shí)例時(shí)會(huì)執(zhí)行構(gòu)造方法。

同時(shí)每個(gè)枚舉實(shí)例都是static final類型的弧岳,也就表明只能被實(shí)例化一次凳忙。在調(diào)用構(gòu)造方法時(shí),我們的單例被實(shí)例化禽炬。

也就是說(shuō)涧卵,因?yàn)閑num中的實(shí)例被保證只會(huì)被實(shí)例化一次,所以我們的INSTANCE也被保證實(shí)例化一次腹尖。


可以看到柳恐,枚舉實(shí)現(xiàn)單例還是比較簡(jiǎn)單的,除此之外我們?cè)賮?lái)看一下Enum這個(gè)類的聲明:

public abstract class Enum<E extends Enum<E>>

implements Comparable<E>, Serializable


可以看到,枚舉也提供了序列化機(jī)制乐设。某些情況讼庇,比如我們要通過(guò)網(wǎng)絡(luò)傳輸一個(gè)數(shù)據(jù)庫(kù)連接的句柄,會(huì)提供很多幫助近尚。

最后借用 《Effective Java》一書中的話蠕啄,

單元素的枚舉類型已經(jīng)成為實(shí)現(xiàn)Singleton的最佳方法。

示例:


/**

* @Title: java單例之enum實(shí)現(xiàn)方式

* @ClassName: EnumSingleton.java

* @Description:

*

* @Copyright 2016-2018 - Powered By 研發(fā)中心

* @author: 王延飛

* @date:? 2018-02-07 20:02

* @version V1.0

*/

public class EnumSingleton{

? ? private EnumSingleton(){}

? ? public static EnumSingleton getInstance(){

? ? ? ? return Singleton.INSTANCE.getInstance();

? ? }

? ? private static enum Singleton{

? ? ? ? INSTANCE;

? ? ? ? private EnumSingleton singleton;

? ? ? ? //JVM會(huì)保證此方法絕對(duì)只調(diào)用一次

? ? ? ? private Singleton(){

? ? ? ? ? ? singleton = new EnumSingleton();

? ? ? ? }

? ? ? ? public EnumSingleton getInstance(){

? ? ? ? ? ? return singleton;

? ? ? ? }

? ? }

? ? public static void main(String[] args) {

? ? ? ? EnumSingleton obj1 = EnumSingleton.getInstance();

? ? ? ? EnumSingleton obj2 = EnumSingleton.getInstance();

? ? ? ? //輸出結(jié)果:obj1==obj2?true

? ? ? ? System.out.println("obj1==obj2?" + (obj1==obj2));

? ? }

}



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

1)需要額外的工作來(lái)實(shí)現(xiàn)序列化戈锻,否則每次反序列化一個(gè)序列化的對(duì)象時(shí)都會(huì)創(chuàng)建一個(gè)新的實(shí)例歼跟。

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


而枚舉類很好的解決了這兩個(gè)問(wèn)題,使用枚舉除了線程安全和防止反射調(diào)用構(gòu)造器之外拒迅,還提供了自動(dòng)序列化機(jī)制骚秦,防止反序列化的時(shí)候創(chuàng)建新的對(duì)象。



6璧微、單例模式的線程安全性

首先要說(shuō)的是單例模式的線程安全意味著:某個(gè)類的實(shí)例在多線程環(huán)境下只會(huì)被創(chuàng)建一次出來(lái)作箍。單例模式有很多種的寫法,我總結(jié)一下:


(1)餓漢式:線程安全


(2)懶漢式:非線程安全


(3)雙檢鎖:線程安全


(4)靜態(tài)內(nèi)部類:線程安全


(5)枚舉:線程安全




參考來(lái)源: http://www.importnew.com/12773.html


參考來(lái)源: http://blog.csdn.net/goodlixueyong/article/details/51935526

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末往毡,一起剝皮案震驚了整個(gè)濱河市蒙揣,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌开瞭,老刑警劉巖懒震,帶你破解...
    沈念sama閱讀 211,639評(píng)論 6 492
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場(chǎng)離奇詭異嗤详,居然都是意外死亡个扰,警方通過(guò)查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 90,277評(píng)論 3 385
  • 文/潘曉璐 我一進(jìn)店門葱色,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái)递宅,“玉大人,你說(shuō)我怎么就攤上這事苍狰“炝洌” “怎么了?”我有些...
    開(kāi)封第一講書人閱讀 157,221評(píng)論 0 348
  • 文/不壞的土叔 我叫張陵淋昭,是天一觀的道長(zhǎng)俐填。 經(jīng)常有香客問(wèn)我,道長(zhǎng)翔忽,這世上最難降的妖魔是什么英融? 我笑而不...
    開(kāi)封第一講書人閱讀 56,474評(píng)論 1 283
  • 正文 為了忘掉前任盏檐,我火速辦了婚禮,結(jié)果婚禮上驶悟,老公的妹妹穿的比我還像新娘胡野。我一直安慰自己,他們只是感情好痕鳍,可當(dāng)我...
    茶點(diǎn)故事閱讀 65,570評(píng)論 6 386
  • 文/花漫 我一把揭開(kāi)白布硫豆。 她就那樣靜靜地躺著,像睡著了一般笼呆。 火紅的嫁衣襯著肌膚如雪够庙。 梳的紋絲不亂的頭發(fā)上,一...
    開(kāi)封第一講書人閱讀 49,816評(píng)論 1 290
  • 那天抄邀,我揣著相機(jī)與錄音,去河邊找鬼昼榛。 笑死境肾,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的胆屿。 我是一名探鬼主播奥喻,決...
    沈念sama閱讀 38,957評(píng)論 3 408
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼非迹!你這毒婦竟也來(lái)了环鲤?” 一聲冷哼從身側(cè)響起,我...
    開(kāi)封第一講書人閱讀 37,718評(píng)論 0 266
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤憎兽,失蹤者是張志新(化名)和其女友劉穎冷离,沒(méi)想到半個(gè)月后,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體纯命,經(jīng)...
    沈念sama閱讀 44,176評(píng)論 1 303
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡西剥,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 36,511評(píng)論 2 327
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了亿汞。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片瞭空。...
    茶點(diǎn)故事閱讀 38,646評(píng)論 1 340
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖疗我,靈堂內(nèi)的尸體忽然破棺而出咆畏,到底是詐尸還是另有隱情,我是刑警寧澤吴裤,帶...
    沈念sama閱讀 34,322評(píng)論 4 330
  • 正文 年R本政府宣布旧找,位于F島的核電站,受9級(jí)特大地震影響嚼摩,放射性物質(zhì)發(fā)生泄漏钦讳。R本人自食惡果不足惜矿瘦,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,934評(píng)論 3 313
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望愿卒。 院中可真熱鬧缚去,春花似錦、人聲如沸琼开。這莊子的主人今日做“春日...
    開(kāi)封第一講書人閱讀 30,755評(píng)論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)柜候。三九已至搞动,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間渣刷,已是汗流浹背鹦肿。 一陣腳步聲響...
    開(kāi)封第一講書人閱讀 31,987評(píng)論 1 266
  • 我被黑心中介騙來(lái)泰國(guó)打工, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留辅柴,地道東北人箩溃。 一個(gè)月前我還...
    沈念sama閱讀 46,358評(píng)論 2 360
  • 正文 我出身青樓,卻偏偏與公主長(zhǎng)得像碌嘀,于是被迫代替她去往敵國(guó)和親涣旨。 傳聞我的和親對(duì)象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 43,514評(píng)論 2 348

推薦閱讀更多精彩內(nèi)容