日韩性视频-久久久蜜桃-www中文字幕-在线中文字幕av-亚洲欧美一区二区三区四区-撸久久-香蕉视频一区-久久无码精品丰满人妻-国产高潮av-激情福利社-日韩av网址大全-国产精品久久999-日本五十路在线-性欧美在线-久久99精品波多结衣一区-男女午夜免费视频-黑人极品ⅴideos精品欧美棵-人人妻人人澡人人爽精品欧美一区-日韩一区在线看-欧美a级在线免费观看

歡迎訪(fǎng)問(wèn) 生活随笔!

生活随笔

當(dāng)前位置: 首頁(yè) > 编程语言 > java >内容正文

java

探讨一下Java单例设计模式

發(fā)布時(shí)間:2023/12/31 java 31 豆豆
生活随笔 收集整理的這篇文章主要介紹了 探讨一下Java单例设计模式 小編覺(jué)得挺不錯(cuò)的,現(xiàn)在分享給大家,幫大家做個(gè)參考.

所謂單例模式,簡(jiǎn)單來(lái)說(shuō),就是在整個(gè)應(yīng)用中保證只有一個(gè)類(lèi)的實(shí)例存在。就像是Java Web中的application,也就是提供了一個(gè)全局變量,用處相當(dāng)廣泛,比如保存全局?jǐn)?shù)據(jù),實(shí)現(xiàn)全局性的操作等。

1. 最簡(jiǎn)單的實(shí)現(xiàn)首先,能夠想到的最簡(jiǎn)單的實(shí)現(xiàn)是,把類(lèi)的構(gòu)造函數(shù)寫(xiě)成private的,從而保證別的類(lèi)不能實(shí)例化此類(lèi),然后在類(lèi)中提供一個(gè)靜態(tài)的實(shí)例并能夠返回給使用者。這樣,使用者就可以通過(guò)這個(gè)引用使用到這個(gè)類(lèi)的實(shí)例了。public class ReYoSingletonClass { private static final ReYoSingletonClass instance = new ReYoSingletonClass(); public static ReYoSingletonClass getInstance() { return instance; } private ReYoSingletonClass() { } }如上例,外部使用者如果需要使用ReYoSingletonClass的實(shí)例,只能通過(guò)getInstance()方法,并且它的構(gòu)造方法是private的,這樣就保證了只能有一個(gè)對(duì)象存在。2. 性能優(yōu)化——lazy loaded上面的代碼雖然簡(jiǎn)單,但是有一個(gè)問(wèn)題——無(wú)論這個(gè)類(lèi)是否被使用,都會(huì)創(chuàng)建一個(gè)instance對(duì)象。如果這個(gè)創(chuàng)建過(guò)程很耗時(shí),比如需要連接10000次數(shù)據(jù)庫(kù)(夸張了…:-)),并且這個(gè)類(lèi)還并不一定會(huì)被使用,那么這個(gè)創(chuàng)建過(guò)程就是無(wú)用的。怎么辦呢?為了解決這個(gè)問(wèn)題,我們想到了新的解決方案:public class ReYoSingletonClass { private static ReYoSingletonClass instance = null; public static ReYoSingletonClass getInstance() { if(instance == null) { instance = new ReYoSingletonClass(); } return instance; } private ReYoSingletonClass() { } }代碼的變化有兩處——首先,把instance初始化為null,直到第一次使用的時(shí)候通過(guò)判斷是否為null來(lái)創(chuàng)建對(duì)象。因?yàn)閯?chuàng)建過(guò)程不在聲明處,所以那個(gè)final的修飾必須去掉。我們來(lái)想象一下這個(gè)過(guò)程。要使用ReYoSingletonClass,調(diào)用getInstance()方法。第一次的時(shí)候發(fā)現(xiàn)instance是null,然后就新建一個(gè)對(duì)象,返回出去;第二次再使用的時(shí)候,因?yàn)檫@個(gè)instance是static的,所以已經(jīng)不是null了,因此不會(huì)再創(chuàng)建對(duì)象,直接將其返回。這個(gè)過(guò)程就成為lazy loaded,也就是遲加載——直到使用的時(shí)候才進(jìn)行加載。3. 同步上面的代碼很清楚,也很簡(jiǎn)單。然而就像那句名言:“80%的錯(cuò)誤都是由20%代碼優(yōu)化引起的”。單線(xiàn)程下,這段代碼沒(méi)有什么問(wèn)題,可是如果是多線(xiàn)程,麻煩就來(lái)了。我們來(lái)分析一下:線(xiàn)程A希望使用ReYoSingletonClass,調(diào)用getInstance()方法。因?yàn)槭堑谝淮握{(diào)用,A就發(fā)現(xiàn)instance是null的,于是它開(kāi)始創(chuàng)建實(shí)例,就在這個(gè)時(shí)候,CPU發(fā)生時(shí)間片切換,線(xiàn)程B開(kāi)始執(zhí)行,它要使用ReYoSingletonClass,調(diào)用getInstance()方法,同樣檢測(cè)到instance是null——注意,這是在A檢測(cè)完之后切換的,也就是說(shuō)A并沒(méi)有來(lái)得及創(chuàng)建對(duì)象——因此B開(kāi)始創(chuàng)建。B創(chuàng)建完成后,切換到A繼續(xù)執(zhí)行,因?yàn)樗呀?jīng)檢測(cè)完了,所以A不會(huì)再檢測(cè)一遍,它會(huì)直接創(chuàng)建對(duì)象。這樣,線(xiàn)程A和B各自擁有一個(gè)ReYoSingletonClass的對(duì)象——單例失敗!解決的方法也很簡(jiǎn)單,那就是加鎖:public class ReYoSingletonClass { private static ReYoSingletonClass instance = null; public synchronized static ReYoSingletonClass getInstance() { if(instance == null) { instance = new ReYoSingletonClass(); } return instance; } private ReYoSingletonClass() { } }是要getInstance()加上同步鎖,一個(gè)線(xiàn)程必須等待另外一個(gè)線(xiàn)程創(chuàng)建完成后才能使用這個(gè)方法,這就保證了單例的唯一性。4. 又是性能上面的代碼又是很清楚很簡(jiǎn)單的,然而,簡(jiǎn)單的東西往往不夠理想。這段代碼毫無(wú)疑問(wèn)存在性能的問(wèn)題——synchronized修飾的同步塊可是要比一般的代碼段慢上幾倍的!如果存在很多次getInstance()的調(diào)用,那性能問(wèn)題就不得不考慮了!讓我們來(lái)分析一下,究竟是整個(gè)方法都必須加鎖,還是僅僅其中某一句加鎖就足夠了?我們?yōu)槭裁匆渔i呢?分析一下出現(xiàn)lazy loaded的那種情形的原因。原因就是檢測(cè)null的操作和創(chuàng)建對(duì)象的操作分離了。如果這兩個(gè)操作能夠原子地進(jìn)行,那么單例就已經(jīng)保證了。于是,我們開(kāi)始修改代碼:public class ReYoSingletonClass { private static ReYoSingletonClass instance = null; public static ReYoSingletonClass getInstance() { synchronized (ReYoSingletonClass.class) { if(instance == null) { instance = new ReYoSingletonClass(); } } return instance; } private ReYoSingletonClass() { } }首先去掉getInstance()的同步操作,然后把同步鎖加載if語(yǔ)句上。但是這樣的修改起不到任何作用:因?yàn)槊看握{(diào)用getInstance()的時(shí)候必然要同步,性能問(wèn)題還是存在。如果……如果我們事先判斷一下是不是為null再去同步呢?public class ReYoSingletonClass { private static ReYoSingletonClass instance = null; public static ReYoSingletonClass getInstance() { if (instance == null) { synchronized (ReYoSingletonClass.class) { if (instance == null) { instance = new ReYoSingletonClass(); } } } return instance; } private ReYoSingletonClass() { } }還有問(wèn)題嗎?首先判斷instance是不是為null,如果為null,加鎖初始化;如果不為null,直接返回instance。這就是double-checked locking設(shè)計(jì)實(shí)現(xiàn)單例模式。到此為止,一切都很完美。我們用一種很聰明的方式實(shí)現(xiàn)了單例模式。5. 從源頭檢查下面我們開(kāi)始說(shuō)編譯原理。所謂編譯,就是把源代碼“翻譯”成目標(biāo)代碼——大多數(shù)是指機(jī)器代碼——的過(guò)程。針對(duì)Java,它的目標(biāo)代碼不是本地機(jī)器代碼,而是虛擬機(jī)代碼。編譯原理里面有一個(gè)很重要的內(nèi)容是編譯器優(yōu)化。所謂編譯器優(yōu)化是指,在不改變?cè)瓉?lái)語(yǔ)義的情況下,通過(guò)調(diào)整語(yǔ)句順序,來(lái)讓程序運(yùn)行的更快。這個(gè)過(guò)程成為reorder。要知道,JVM只是一個(gè)標(biāo)準(zhǔn),并不是實(shí)現(xiàn)。JVM中并沒(méi)有規(guī)定有關(guān)編譯器優(yōu)化的內(nèi)容,也就是說(shuō),JVM實(shí)現(xiàn)可以自由的進(jìn)行編譯器優(yōu)化。下面來(lái)想一下,創(chuàng)建一個(gè)變量需要哪些步驟呢?一個(gè)是申請(qǐng)一塊內(nèi)存,調(diào)用構(gòu)造方法進(jìn)行初始化操作,另一個(gè)是分配一個(gè)指針指向這塊內(nèi)存。這兩個(gè)操作誰(shuí)在前誰(shuí)在后呢?JVM規(guī)范并沒(méi)有規(guī)定。那么就存在這么一種情況,JVM是先開(kāi)辟出一塊內(nèi)存,然后把指針指向這塊內(nèi)存,最后調(diào)用構(gòu)造方法進(jìn)行初始化。下面我們來(lái)考慮這么一種情況:線(xiàn)程A開(kāi)始創(chuàng)建ReYoSingletonClass的實(shí)例,此時(shí)線(xiàn)程B調(diào)用了getInstance()方法,首先判斷instance是否為null。按照我們上面所說(shuō)的內(nèi)存模型,A已經(jīng)把instance指向了那塊內(nèi)存,只是還沒(méi)有調(diào)用構(gòu)造方法,因此B檢測(cè)到instance不為null,于是直接把instance返回了——問(wèn)題出現(xiàn)了,盡管instance不為null,但它并沒(méi)有構(gòu)造完成,就像一套房子已經(jīng)給了你鑰匙,但你并不能住進(jìn)去,因?yàn)槔锩孢€沒(méi)有收拾。此時(shí),如果B在A將instance構(gòu)造完成之前就是用了這個(gè)實(shí)例,程序就會(huì)出現(xiàn)錯(cuò)誤了!于是,我們想到了下面的代碼:public class ReYoSingletonClass { private static ReYoSingletonClass instance = null; public static ReYoSingletonClass getInstance() { if (instance == null) { ReYoSingletonClass sc; synchronized (ReYoSingletonClass.class) { sc = instance; if (sc == null) { synchronized (ReYoSingletonClass.class) { if(sc == null) { sc = new ReYoSingletonClass(); } } instance = sc; } } } return instance; } private ReYoSingletonClass() { } }我們?cè)诘谝粋€(gè)同步塊里面創(chuàng)建一個(gè)臨時(shí)變量,然后使用這個(gè)臨時(shí)變量進(jìn)行對(duì)象的創(chuàng)建,并且在最后把instance指針臨時(shí)變量的內(nèi)存空間。寫(xiě)出這種代碼基于以下思想,即synchronized會(huì)起到一個(gè)代碼屏蔽的作用,同步塊里面的代碼和外部的代碼沒(méi)有聯(lián)系。因此,在外部的同步塊里面對(duì)臨時(shí)變量sc進(jìn)行操作并不影響instance,所以外部類(lèi)在instance=sc;之前檢測(cè)instance的時(shí)候,結(jié)果instance依然是null。不過(guò),這種想法完全是錯(cuò)誤的!同步塊的釋放保證在此之前——也就是同步塊里面——的操作必須完成,但是并不保證同步塊之后的操作不能因編譯器優(yōu)化而調(diào)換到同步塊結(jié)束之前進(jìn)行。因此,編譯器完全可以把instance=sc;這句移到內(nèi)部同步塊里面執(zhí)行。這樣,程序又是錯(cuò)誤的了!6. 解決方案說(shuō)了這么多,難道單例沒(méi)有辦法在Java中實(shí)現(xiàn)嗎?其實(shí)不然!在JDK 5之后,Java使用了新的內(nèi)存模型。volatile關(guān)鍵字有了明確的語(yǔ)義——在JDK1.5之前,volatile是個(gè)關(guān)鍵字,但是并沒(méi)有明確的規(guī)定其用途——被volatile修飾的寫(xiě)變量不能和之前的讀寫(xiě)代碼調(diào)整,讀變量不能和之后的讀寫(xiě)代碼調(diào)整!因此,只要我們簡(jiǎn)單的把instance加上volatile關(guān)鍵字就可以了。public class ReYoSingletonClass { private volatile static ReYoSingletonClass instance = null; public static ReYoSingletonClass getInstance() { if (instance == null) { synchronized (ReYoSingletonClass.class) { if(instance == null) { instance = new ReYoSingletonClass(); } } } return instance; } private ReYoSingletonClass() { } }然而,這只是JDK1.5之后的Java的解決方案,那之前版本呢?其實(shí),還有另外的一種解決方案,并不會(huì)受到Java版本的影響:public class ReYoSingletonClass { private static class ReYoSingletonClassInstance { private static final ReYoSingletonClass instance = new ReYoSingletonClass(); } public static ReYoSingletonClass getInstance() { return ReYoSingletonClassInstance.instance; } private ReYoSingletonClass() { } }在這一版本的單例模式實(shí)現(xiàn)代碼中,我們使用了Java的靜態(tài)內(nèi)部類(lèi)。這一技術(shù)是被JVM明確說(shuō)明了的,因此不存在任何二義性。在這段代碼中,因?yàn)镽eYoSingletonClass沒(méi)有static的屬性,因此并不會(huì)被初始化。直到調(diào)用getInstance()的時(shí)候,會(huì)首先加載ReYoSingletonClassInstance類(lèi),這個(gè)類(lèi)有一個(gè)static的ReYoSingletonClass實(shí)例,因此需要調(diào)用ReYoSingletonClass的構(gòu)造方法,然后getInstance()將把這個(gè)內(nèi)部類(lèi)的instance返回給使用者。由于這個(gè)instance是static的,因此并不會(huì)構(gòu)造多次。由于ReYoSingletonClassInstance是私有靜態(tài)內(nèi)部類(lèi),所以不會(huì)被其他類(lèi)知道,同樣,static語(yǔ)義也要求不會(huì)有多個(gè)實(shí)例存在。并且,JSL規(guī)范定義,類(lèi)的構(gòu)造必須是原子性的,非并發(fā)的,因此不需要加同步塊。同樣,由于這個(gè)構(gòu)造是并發(fā)的,所以getInstance()也并不需要加同步。至此,我們完整的了解了單例模式在Java語(yǔ)言中的時(shí)候,提出了兩種解決方案。個(gè)人偏向于第二種,并且Effiective Java也推薦的這種方式。

?

然后我們來(lái)看一下性能差:

?

第二次方式 :

第一種方式:

?


1. 最簡(jiǎn)單的實(shí)現(xiàn)
?
首先,能夠想到的最簡(jiǎn)單的實(shí)現(xiàn)是,把類(lèi)的構(gòu)造函數(shù)寫(xiě)成private的,從而保證別的類(lèi)不能實(shí)例化此類(lèi),然后在類(lèi)中提供一個(gè)靜態(tài)的實(shí)例并能夠返回給使用者。這樣,使用者就可以通過(guò)這個(gè)引用使用到這個(gè)類(lèi)的實(shí)例了。
?
public class ReYoSingletonClass {

? private static final ReYoSingletonClass instance = new ReYoSingletonClass();
?? ?
? public static ReYoSingletonClass getInstance() {
??? return instance;
? }
?? ?
? private ReYoSingletonClass() {
??? ?
? }
?? ?
}
?
如上例,外部使用者如果需要使用ReYoSingletonClass的實(shí)例,只能通過(guò)getInstance()方法,并且它的構(gòu)造方法是private的,這樣就保證了只能有一個(gè)對(duì)象存在。
?
2. 性能優(yōu)化——lazy loaded
?
上面的代碼雖然簡(jiǎn)單,但是有一個(gè)問(wèn)題——無(wú)論這個(gè)類(lèi)是否被使用,都會(huì)創(chuàng)建一個(gè)instance對(duì)象。如果這個(gè)創(chuàng)建過(guò)程很耗時(shí),比如需要連接10000次數(shù)據(jù)庫(kù)(夸張了…:-)),并且這個(gè)類(lèi)還并不一定會(huì)被使用,那么這個(gè)創(chuàng)建過(guò)程就是無(wú)用的。怎么辦呢?
?
為了解決這個(gè)問(wèn)題,我們想到了新的解決方案:
?
public class ReYoSingletonClass {

? private static ReYoSingletonClass instance = null;
?? ?
? public static ReYoSingletonClass getInstance() {
??? if(instance == null) {
????? instance = new ReYoSingletonClass();
??? }
??? return instance;
? }
?? ?
? private ReYoSingletonClass() {
??? ?
? }
?? ?
}
?
代碼的變化有兩處——首先,把instance初始化為null,直到第一次使用的時(shí)候通過(guò)判斷是否為null來(lái)創(chuàng)建對(duì)象。因?yàn)閯?chuàng)建過(guò)程不在聲明處,所以那個(gè)final的修飾必須去掉。
?
我們來(lái)想象一下這個(gè)過(guò)程。要使用ReYoSingletonClass,調(diào)用getInstance()方法。第一次的時(shí)候發(fā)現(xiàn)instance是null,然后就新建一個(gè)對(duì)象,返回出去;第二次再使用的時(shí)候,因?yàn)檫@個(gè)instance是static的,所以已經(jīng)不是null了,因此不會(huì)再創(chuàng)建對(duì)象,直接將其返回。
?
這個(gè)過(guò)程就成為lazy loaded,也就是遲加載——直到使用的時(shí)候才進(jìn)行加載。
?
3. 同步
?
上面的代碼很清楚,也很簡(jiǎn)單。然而就像那句名言:“80%的錯(cuò)誤都是由20%代碼優(yōu)化引起的”。單線(xiàn)程下,這段代碼沒(méi)有什么問(wèn)題,可是如果是多線(xiàn)程,麻煩就來(lái)了。我們來(lái)分析一下:
?
線(xiàn)程A希望使用ReYoSingletonClass,調(diào)用getInstance()方法。因?yàn)槭堑谝淮握{(diào)用,A就發(fā)現(xiàn)instance是null的,于是它開(kāi)始創(chuàng)建實(shí)例,就在這個(gè)時(shí)候,CPU發(fā)生時(shí)間片切換,線(xiàn)程B開(kāi)始執(zhí)行,它要使用ReYoSingletonClass,調(diào)用getInstance()方法,同樣檢測(cè)到instance是null——注意,這是在A檢測(cè)完之后切換的,也就是說(shuō)A并沒(méi)有來(lái)得及創(chuàng)建對(duì)象——因此B開(kāi)始創(chuàng)建。B創(chuàng)建完成后,切換到A繼續(xù)執(zhí)行,因?yàn)樗呀?jīng)檢測(cè)完了,所以A不會(huì)再檢測(cè)一遍,它會(huì)直接創(chuàng)建對(duì)象。這樣,線(xiàn)程A和B各自擁有一個(gè)ReYoSingletonClass的對(duì)象——單例失敗!
?
解決的方法也很簡(jiǎn)單,那就是加鎖:
?
public class ReYoSingletonClass {

? private static ReYoSingletonClass instance = null;
?? ?
? public synchronized static ReYoSingletonClass getInstance() {
??? if(instance == null) {
????? instance = new ReYoSingletonClass();
??? }
??? return instance;
? }
?? ?
? private ReYoSingletonClass() {
??? ?
? }
?? ?
}
?
是要getInstance()加上同步鎖,一個(gè)線(xiàn)程必須等待另外一個(gè)線(xiàn)程創(chuàng)建完成后才能使用這個(gè)方法,這就保證了單例的唯一性。
?
4. 又是性能
?
上面的代碼又是很清楚很簡(jiǎn)單的,然而,簡(jiǎn)單的東西往往不夠理想。這段代碼毫無(wú)疑問(wèn)存在性能的問(wèn)題——synchronized修飾的同步塊可是要比一般的代碼段慢上幾倍的!如果存在很多次getInstance()的調(diào)用,那性能問(wèn)題就不得不考慮了!
?
讓我們來(lái)分析一下,究竟是整個(gè)方法都必須加鎖,還是僅僅其中某一句加鎖就足夠了?我們?yōu)槭裁匆渔i呢?分析一下出現(xiàn)lazy loaded的那種情形的原因。原因就是檢測(cè)null的操作和創(chuàng)建對(duì)象的操作分離了。如果這兩個(gè)操作能夠原子地進(jìn)行,那么單例就已經(jīng)保證了。于是,我們開(kāi)始修改代碼:
?
public class ReYoSingletonClass {

? private static ReYoSingletonClass instance = null;
?? ?
? public static ReYoSingletonClass getInstance() {
??? synchronized (ReYoSingletonClass.class) {
????? if(instance == null) {
??????? instance = new ReYoSingletonClass();
????? }
??? }??? ?
??? return instance;
? }
?? ?
? private ReYoSingletonClass() {
??? ?
? }
?? ?
}
?
首先去掉getInstance()的同步操作,然后把同步鎖加載if語(yǔ)句上。但是這樣的修改起不到任何作用:因?yàn)槊看握{(diào)用getInstance()的時(shí)候必然要同步,性能問(wèn)題還是存在。如果……如果我們事先判斷一下是不是為null再去同步呢?
?
public class ReYoSingletonClass {

? private static ReYoSingletonClass instance = null;

? public static ReYoSingletonClass getInstance() {
??? if (instance == null) {
????? synchronized (ReYoSingletonClass.class) {
??????? if (instance == null) {
????????? instance = new ReYoSingletonClass();
??????? }
????? }
??? }
??? return instance;
? }

? private ReYoSingletonClass() {

? }

}
?
還有問(wèn)題嗎?首先判斷instance是不是為null,如果為null,加鎖初始化;如果不為null,直接返回instance。
?
這就是double-checked locking設(shè)計(jì)實(shí)現(xiàn)單例模式。到此為止,一切都很完美。我們用一種很聰明的方式實(shí)現(xiàn)了單例模式。
?
5. 從源頭檢查
?
下面我們開(kāi)始說(shuō)編譯原理。所謂編譯,就是把源代碼“翻譯”成目標(biāo)代碼——大多數(shù)是指機(jī)器代碼——的過(guò)程。針對(duì)Java,它的目標(biāo)代碼不是本地機(jī)器代碼,而是虛擬機(jī)代碼。編譯原理里面有一個(gè)很重要的內(nèi)容是編譯器優(yōu)化。所謂編譯器優(yōu)化是指,在不改變?cè)瓉?lái)語(yǔ)義的情況下,通過(guò)調(diào)整語(yǔ)句順序,來(lái)讓程序運(yùn)行的更快。這個(gè)過(guò)程成為reorder。
?
要知道,JVM只是一個(gè)標(biāo)準(zhǔn),并不是實(shí)現(xiàn)。JVM中并沒(méi)有規(guī)定有關(guān)編譯器優(yōu)化的內(nèi)容,也就是說(shuō),JVM實(shí)現(xiàn)可以自由的進(jìn)行編譯器優(yōu)化。
?
下面來(lái)想一下,創(chuàng)建一個(gè)變量需要哪些步驟呢?一個(gè)是申請(qǐng)一塊內(nèi)存,調(diào)用構(gòu)造方法進(jìn)行初始化操作,另一個(gè)是分配一個(gè)指針指向這塊內(nèi)存。這兩個(gè)操作誰(shuí)在前誰(shuí)在后呢?JVM規(guī)范并沒(méi)有規(guī)定。那么就存在這么一種情況,JVM是先開(kāi)辟出一塊內(nèi)存,然后把指針指向這塊內(nèi)存,最后調(diào)用構(gòu)造方法進(jìn)行初始化。
?
下面我們來(lái)考慮這么一種情況:線(xiàn)程A開(kāi)始創(chuàng)建ReYoSingletonClass的實(shí)例,此時(shí)線(xiàn)程B調(diào)用了getInstance()方法,首先判斷instance是否為null。按照我們上面所說(shuō)的內(nèi)存模型,A已經(jīng)把instance指向了那塊內(nèi)存,只是還沒(méi)有調(diào)用構(gòu)造方法,因此B檢測(cè)到instance不為null,于是直接把instance返回了——問(wèn)題出現(xiàn)了,盡管instance不為null,但它并沒(méi)有構(gòu)造完成,就像一套房子已經(jīng)給了你鑰匙,但你并不能住進(jìn)去,因?yàn)槔锩孢€沒(méi)有收拾。此時(shí),如果B在A將instance構(gòu)造完成之前就是用了這個(gè)實(shí)例,程序就會(huì)出現(xiàn)錯(cuò)誤了!
?
于是,我們想到了下面的代碼:
?
public class ReYoSingletonClass {

? private static ReYoSingletonClass instance = null;

? public static ReYoSingletonClass getInstance() {
??? if (instance == null) {
????? ReYoSingletonClass sc;
????? synchronized (ReYoSingletonClass.class) {
??????? sc = instance;
??????? if (sc == null) {
????????? synchronized (ReYoSingletonClass.class) {
??????????? if(sc == null) {
????????????? sc = new ReYoSingletonClass();
??????????? }
????????? }
????????? instance = sc;
??????? }
????? }
??? }
??? return instance;
? }

? private ReYoSingletonClass() {

? }
?? ?
}
?
我們?cè)诘谝粋€(gè)同步塊里面創(chuàng)建一個(gè)臨時(shí)變量,然后使用這個(gè)臨時(shí)變量進(jìn)行對(duì)象的創(chuàng)建,并且在最后把instance指針臨時(shí)變量的內(nèi)存空間。寫(xiě)出這種代碼基于以下思想,即synchronized會(huì)起到一個(gè)代碼屏蔽的作用,同步塊里面的代碼和外部的代碼沒(méi)有聯(lián)系。因此,在外部的同步塊里面對(duì)臨時(shí)變量sc進(jìn)行操作并不影響instance,所以外部類(lèi)在instance=sc;之前檢測(cè)instance的時(shí)候,結(jié)果instance依然是null。
?
不過(guò),這種想法完全是錯(cuò)誤的!同步塊的釋放保證在此之前——也就是同步塊里面——的操作必須完成,但是并不保證同步塊之后的操作不能因編譯器優(yōu)化而調(diào)換到同步塊結(jié)束之前進(jìn)行。因此,編譯器完全可以把instance=sc;這句移到內(nèi)部同步塊里面執(zhí)行。這樣,程序又是錯(cuò)誤的了!
?
6. 解決方案
?
說(shuō)了這么多,難道單例沒(méi)有辦法在Java中實(shí)現(xiàn)嗎?其實(shí)不然!
?
在JDK 5之后,Java使用了新的內(nèi)存模型。volatile關(guān)鍵字有了明確的語(yǔ)義——在JDK1.5之前,volatile是個(gè)關(guān)鍵字,但是并沒(méi)有明確的規(guī)定其用途——被volatile修飾的寫(xiě)變量不能和之前的讀寫(xiě)代碼調(diào)整,讀變量不能和之后的讀寫(xiě)代碼調(diào)整!因此,只要我們簡(jiǎn)單的把instance加上volatile關(guān)鍵字就可以了。
?
public class ReYoSingletonClass {

? private volatile static ReYoSingletonClass instance = null;

? public static ReYoSingletonClass getInstance() {
??? if (instance == null) {
????? synchronized (ReYoSingletonClass.class) {
??????? if(instance == null) {
????????? instance = new ReYoSingletonClass();
??????? }
????? }
??? }
??? return instance;
? }

? private ReYoSingletonClass() {

? }
?? ?
}
?
然而,這只是JDK1.5之后的Java的解決方案,那之前版本呢?其實(shí),還有另外的一種解決方案,并不會(huì)受到Java版本的影響:
?
public class ReYoSingletonClass {
?? ?
? private static class ReYoSingletonClassInstance {
??? private static final ReYoSingletonClass instance = new ReYoSingletonClass();
? }

? public static ReYoSingletonClass getInstance() {
??? return ReYoSingletonClassInstance.instance;
? }

? private ReYoSingletonClass() {

? }
?? ?
}
?
在這一版本的單例模式實(shí)現(xiàn)代碼中,我們使用了Java的靜態(tài)內(nèi)部類(lèi)。這一技術(shù)是被JVM明確說(shuō)明了的,因此不存在任何二義性。在這段代碼中,因?yàn)镽eYoSingletonClass沒(méi)有static的屬性,因此并不會(huì)被初始化。直到調(diào)用getInstance()的時(shí)候,會(huì)首先加載ReYoSingletonClassInstance類(lèi),這個(gè)類(lèi)有一個(gè)static的ReYoSingletonClass實(shí)例,因此需要調(diào)用ReYoSingletonClass的構(gòu)造方法,然后getInstance()將把這個(gè)內(nèi)部類(lèi)的instance返回給使用者。由于這個(gè)instance是static的,因此并不會(huì)構(gòu)造多次。
?
由于ReYoSingletonClassInstance是私有靜態(tài)內(nèi)部類(lèi),所以不會(huì)被其他類(lèi)知道,同樣,static語(yǔ)義也要求不會(huì)有多個(gè)實(shí)例存在。并且,JSL規(guī)范定義,類(lèi)的構(gòu)造必須是原子性的,非并發(fā)的,因此不需要加同步塊。同樣,由于這個(gè)構(gòu)造是并發(fā)的,所以getInstance()也并不需要加同步。
?
至此,我們完整的了解了單例模式在Java語(yǔ)言中的時(shí)候,提出了兩種解決方案。個(gè)人偏向于第二種,并且Effiective Java也推薦的這種方式。

總結(jié)

以上是生活随笔為你收集整理的探讨一下Java单例设计模式的全部?jī)?nèi)容,希望文章能夠幫你解決所遇到的問(wèn)題。

如果覺(jué)得生活随笔網(wǎng)站內(nèi)容還不錯(cuò),歡迎將生活随笔推薦給好友。