所謂單例就是在系統(tǒng)中只有一個(gè)該類(lèi)的實(shí)例。
單例模式的核心分以下三個(gè)步驟:
* 構(gòu)造方法私有化。即不能在類(lèi)外實(shí)例化,只能在類(lèi)內(nèi)實(shí)例化。
* 在本類(lèi)中創(chuàng)建本類(lèi)的實(shí)例。
* 在本類(lèi)中提供給外部獲取實(shí)例的方式。
單例模式的實(shí)現(xiàn)方式有兩種:餓漢模式和懶漢模式。
餓漢模式
不管現(xiàn)在需不需要,先創(chuàng)建實(shí)例。關(guān)鍵在于“餓”,餓了就要立即吃。
靜態(tài)常量
這里將類(lèi)的構(gòu)造器私有化,就不能在外部通過(guò)new關(guān)鍵字創(chuàng)建該類(lèi)的實(shí)例,然后定義了一個(gè)該類(lèi)的常量,用static修飾,以便外部能夠獲得該類(lèi)實(shí)例(通過(guò)HungryStaticConstantSingleton.
INSTANCE獲得)。也可以不加final關(guān)鍵字,具體看自己的需求。
1 /** 2 * 惡漢模式-靜態(tài)常量,簡(jiǎn)潔直觀 3 */ 4 public class
HungryStaticConstantSingleton{ 5 //構(gòu)造器私有化 6 private
HungryStaticConstantSingleton() { 7 } 8 //靜態(tài)變量保存實(shí)例變量 并提供給外部實(shí)例 9 public final
static HungryStaticConstantSingleton INSTANCE = new
HungryStaticConstantSingleton();10 }
?
枚舉
這種方式是最簡(jiǎn)潔的,不需要考慮構(gòu)造方法私有化。值得注意的是枚舉類(lèi)不允許被繼承,因?yàn)槊杜e類(lèi)編譯后默認(rèn)為final
class,可防止被子類(lèi)修改。常量類(lèi)可被繼承修改、增加字段等,容易導(dǎo)致父類(lèi)的不兼容。
/** * 惡漢-枚舉形式,最簡(jiǎn)潔 */ public enum HungryEnumSingleton{ INSTANCE; public void
print(){ System.out.println("這是通過(guò)枚舉獲得的實(shí)例"); System.out.println(
"HungryEnumSingleton.pring()"); } }
?
Test,打印實(shí)例直接輸出了【INSTANCE】,是因?yàn)槊杜e幫我們實(shí)現(xiàn)了toString,默認(rèn)打印名稱(chēng)。
public class EnumSingleton2Test{ public static void main(String[] args) {
HungryEnumSingleton singleton2= HungryEnumSingleton.INSTANCE;
System.out.println(singleton2); singleton2.print(); } }
?輸出結(jié)果
?
靜態(tài)代碼塊
這種方式和上面的靜態(tài)常量/變量類(lèi)似,只不過(guò)把new放到了靜態(tài)代碼塊里,從簡(jiǎn)潔程度上比不過(guò)第一種。但是把new放在static代碼塊有別的好處,那就是可以做一些別的操作,如初始化一些變量,從配置文件讀一些數(shù)據(jù)等。
/** * 惡漢模式-靜態(tài)代碼塊 */ public class HungryStaticBlockSingleton{ //構(gòu)造器私有化 private
HungryStaticBlockSingleton() { }//靜態(tài)變量保存實(shí)例變量 public static final
HungryStaticBlockSingleton INSTANCE;static { INSTANCE = new
HungryStaticBlockSingleton(); } }
?
如下,在static代碼塊里讀取?info.properties 配置文件動(dòng)態(tài)配置的屬性,賦值給 info 字段。
/** * 惡漢模式-靜態(tài)代碼塊 * 這種用于可以在靜態(tài)代碼塊進(jìn)行一些初始化 */ public class
HungryStaticBlockSingleton{private String info; private
HungryStaticBlockSingleton(String info) {this.info = info; } //構(gòu)造器私有化 private
HungryStaticBlockSingleton() { }//靜態(tài)變量保存實(shí)例變量 public static final
HungryStaticBlockSingleton INSTANCE;static { Properties properties = new
Properties();try { properties.load(HungryStaticBlockSingleton.class
.getClassLoader().getResourceAsStream("info.properties")); } catch (IOException
e) { e.printStackTrace(); } INSTANCE= new
HungryStaticBlockSingleton(properties.getProperty("info")); } public String
getInfo() {return info; } public void setInfo(String info) { this.info = info;
} }
?
Test,
public class HungrySingletonTest{ public static void main(String[] args) {
HungryStaticBlockSingleton hun= HungryStaticBlockSingleton.INSTANCE;
System.out.println(hun.getInfo()); } }
?
輸出
?
懶漢模式
需要時(shí)再創(chuàng)建,關(guān)鍵在于“懶”,類(lèi)似懶加載。
非線程安全
同樣是構(gòu)造方法私有化,提供給外部獲得實(shí)例的方法,getInstance()方法被調(diào)用時(shí)創(chuàng)建實(shí)例。該方式適用于單線程,因?yàn)樵诙嗑€程的情況下可能會(huì)發(fā)生線程安全問(wèn)題,導(dǎo)致創(chuàng)建不同實(shí)例的情況發(fā)生??梢钥聪旅娴难菔?。
1 /** 2 * 懶漢模式-線程不安全的,適用于單線程 3 */ 4 public class LazyUnsafeSingleton{ 5
private LazyUnsafeSingleton(){ 6 } 7 private static LazyUnsafeSingleton
instance; 8 public static LazyUnsafeSingleton getInstance(){ 9 if(instance==
null){ 10 instance = new LazyUnsafeSingleton(); 11 } 12 return instance; 13 }
14 }
非線程安全演示
1 public class LazyUnsafeSingletionTest{ 2 public static void main(String[]
args)throws ExecutionException, InterruptedException { 3 ExecutorService es =
Executors.newFixedThreadPool(2); 4 Callable<LazyUnsafeSingleton> c1 = new
Callable<LazyUnsafeSingleton>(){ 5 @Override 6 public LazyUnsafeSingleton
call()throws Exception { 7 return LazyUnsafeSingleton.getInstance(); 8 } 9
};10 Callable<LazyUnsafeSingleton> c2 = new Callable<LazyUnsafeSingleton>(){ 11
@Override12 public LazyUnsafeSingleton call() throws Exception { 13 return
LazyUnsafeSingleton.getInstance();14 } 15 }; 16 Future<LazyUnsafeSingleton>
submit = es.submit(c1); 17 Future<LazyUnsafeSingleton> submit1 = es.submit(c2);
18 LazyUnsafeSingleton lazyUnsafeSingleton = submit.get(); 19
LazyUnsafeSingleton lazyUnsafeSingleton1 = submit1.get(); 20 es.shutdown(); 21
22 System.out.println(lazyUnsafeSingleton); 23
System.out.println(lazyUnsafeSingleton);24
System.out.println(lazyUnsafeSingleton1==lazyUnsafeSingleton); 25 } 26 }
?
輸出?大概運(yùn)行三次就會(huì)出現(xiàn)一次,我們可以在?LazyUnsafeSingleton 中判斷?if(instance==null)
之后增加線程休眠以獲得更好的效果。
線程安全的
該方式是懶漢模式中線程安全的創(chuàng)建方式。通過(guò)同步代碼塊控制并發(fā)創(chuàng)建實(shí)例。并且采用雙重檢驗(yàn),當(dāng)兩個(gè)線程同時(shí)執(zhí)行第一個(gè)判空時(shí),都滿(mǎn)足的情況下,都會(huì)進(jìn)來(lái),然后去爭(zhēng)鎖,假設(shè)線程1拿到了鎖,執(zhí)行同步代碼塊的內(nèi)容,創(chuàng)建了實(shí)例并返回,此時(shí)線程2又獲得鎖,執(zhí)行同步代碼塊內(nèi)的代碼,因?yàn)榇藭r(shí)線程1已經(jīng)創(chuàng)建了,所以線程2雖然拿到鎖了,如果內(nèi)部不加判空的話,線程2會(huì)再new一次,導(dǎo)致兩個(gè)線程獲得的不是同一個(gè)實(shí)例。線程安全的控制其實(shí)是內(nèi)部判空在起作用,至于為什么要加外面的判空下面會(huì)說(shuō)。
/** * 懶漢模式-線程安全,適用于多線程 */ public class LazySafeSingleton{ private static
volatile LazySafeSingleton safeSingleton;//防止指令重排 private LazySafeSingleton() {
}public static LazySafeSingleton getInstance(){ if(safeSingleton==null){
synchronized (LazySafeSingleton.class){ if(safeSingleton==null){//雙重檢測(cè)
safeSingleton =new LazySafeSingleton(); } } } return safeSingleton; } }
?當(dāng)不加內(nèi)層判空時(shí),會(huì)出現(xiàn)不是單例的情況,只不過(guò)出現(xiàn)的概率更低了點(diǎn)。
可不可以只加內(nèi)層判空呢?答案是可以。
那為什么還要加外層判空的呢?
內(nèi)層判空已經(jīng)可以滿(mǎn)足線程安全了,加外層判空的目的是為了提高效率。因?yàn)榭赡艽嬖谶@樣的情況:線程1拿到鎖后執(zhí)行同步代碼塊,在new之后,還沒(méi)有釋放鎖的時(shí)候,線程2過(guò)來(lái)了,它在等待鎖(此時(shí)線程1已經(jīng)創(chuàng)建了實(shí)例,只不過(guò)還沒(méi)釋放鎖,線程2就來(lái)了),然后線程1釋放鎖后,線程2拿到鎖,進(jìn)入同步代碼塊匯總,判空,返回。這種情況線程2是不是不用去等待鎖了?所以在外層又加了一個(gè)判空就是為了防止這種情況,線程2過(guò)來(lái)后先判空,不為空就不用去等待鎖了,這樣提高了效率。
內(nèi)部類(lèi)創(chuàng)建外部類(lèi)實(shí)例
該方式天然線程安全,是否final根據(jù)自己需要。
1 /** 2 * 懶漢模式-線程安全,適用于多線程 3 * 在內(nèi)部類(lèi)被加載和初始化時(shí) 才創(chuàng)建實(shí)例 4 *
靜態(tài)內(nèi)部類(lèi)不會(huì)自動(dòng)隨著外部類(lèi)的加載和初始化而初始化,它是要單獨(dú)加載和初始化的。 5 * 因?yàn)槭窃趦?nèi)部類(lèi)加載和初始化時(shí)創(chuàng)建的 因此它是線程安全的 6 */
7 public class LazyInnerSingleton{ 8 private LazyInnerSingleton() { 9 } 10
private static class Inner{ 11 private static final LazyInnerSingleton INSTANCE
=new LazyInnerSingleton(); 12 } 13 public static LazyInnerSingleton
getInstance(){14 return Inner.INSTANCE; 15 } 16 }
?
總結(jié)
餓漢模式
* 靜態(tài)常量 簡(jiǎn)潔直觀容易理解
* 枚舉 最簡(jiǎn)潔
* 靜態(tài)代碼塊 可以在靜態(tài)塊里做一些初始化的工作
懶漢模式
* 單線程形式 該形式下不適用多線程,存在線程安全問(wèn)題
* 多線程形式 適用于多線程
* 內(nèi)部類(lèi)形式 最簡(jiǎn)潔
?
熱門(mén)工具 換一換
