一、餓漢式單例類 復(fù)制代碼 代碼如下: public class Singleton { private Singleton(){ } private static Singleton instance = new Singleton(); private static Singleton getInstance(){ return instance; } } 特點(diǎn):餓漢式提前實(shí)例化,沒(méi)有懶漢式中多線程問(wèn)題,但不管我們是不是調(diào)用getInstance()都會(huì)存在一個(gè)實(shí)例在內(nèi)存中 二、內(nèi)部類式單例類 復(fù)制代碼 代碼如下: public class Singleton { private Singleton(){ } private class SingletonHoledr(){ private static Singleton instance = new Singleton(); } private static Singleton getInstance(){ return SingletonHoledr.instance; } } 特點(diǎn):內(nèi)部類式中,實(shí)現(xiàn)了延遲加載,只有我們調(diào)用了getInstance(),才會(huì)創(chuàng)建唯一的實(shí)例到內(nèi)存中.并且也解決了懶漢式中多線程的問(wèn)題.解決的方式是利用了Classloader的特性. 三、懶漢式單例類 復(fù)制代碼 代碼如下: public class Singleton { private Singleton(){ } private static Singleton instance; public static Singleton getInstance(){ if(instance == null){ return instance = new Singleton(); }else{ return instance; } } } 特點(diǎn):在懶漢式中,有線程A和B,當(dāng)線程A運(yùn)行到第8行時(shí),跳到線程B,當(dāng)B也運(yùn)行到8行時(shí),兩個(gè)線程的instance都為空,這樣就會(huì)生成兩個(gè)實(shí)例。解決的辦法是同步: 可以同步但是效率不高: 復(fù)制代碼 代碼如下: public class Singleton { private Singleton(){ } private static Singleton instance; public static synchronized Singleton getInstance(){ if(instance == null){ return instance = new Singleton(); }else{ return instance; } } } 這樣寫(xiě)程序不會(huì)出錯(cuò),因?yàn)檎麄€(gè)getInstance是一個(gè)整體的"critical section",但就是效率很不好,因?yàn)槲覀兊哪康钠鋵?shí)只是在第一個(gè)初始化instance的時(shí)候需要locking(加鎖),而后面取用instance的時(shí)候,根本不需要線程同步。 于是聰明的人們想出了下面的做法: 雙檢鎖寫(xiě)法: 復(fù)制代碼 代碼如下: public class Singleton{ private static Singleton single; //聲明靜態(tài)的單例對(duì)象的變量 private Singleton(){} //私有構(gòu)造方法 public static Singleton getSingle(){ //外部通過(guò)此方法可以獲取對(duì)象 if(single == null){ synchronized (Singleton.class) { //保證了同一時(shí)間只能只能有一個(gè)對(duì)象訪問(wèn)此同步塊 if(single == null){ single = new Singleton(); } } } return single; //返回創(chuàng)建好的對(duì)象 } } 思路很簡(jiǎn)單,就是我們只需要同步(synchronize)初始化instance的那部分代碼從而使代碼既正確又很有效率。 原因在于:instance = new Singleton()這行代碼在不同編譯器上的行為是無(wú)法預(yù)知的。一個(gè)優(yōu)化編譯器可以合法地如下實(shí)現(xiàn)instance = new Singleton(): 1. instance = 給新的實(shí)體分配內(nèi)存 2. 調(diào)用Singleton的構(gòu)造函數(shù)來(lái)初始化instance的成員變量 現(xiàn)在想象一下有線程A和B在調(diào)用getInstance,線程A先進(jìn)入,在執(zhí)行到步驟1的時(shí)候被踢出了cpu。然后線程B進(jìn)入,B看到的是instance 已經(jīng)不是null了(內(nèi)存已經(jīng)分配),于是它開(kāi)始放心地使用instance,但這個(gè)是錯(cuò)誤的,因?yàn)樵谶@一時(shí)刻,instance的成員變量還都是缺省值,A還沒(méi)有來(lái)得及執(zhí)行步驟2來(lái)完成instance的初始化。 當(dāng)然編譯器也可以這樣實(shí)現(xiàn): 1. temp = 分配內(nèi)存 2. 調(diào)用temp的構(gòu)造函數(shù) 3. instance = temp 如果編譯器的行為是這樣的話我們似乎就沒(méi)有問(wèn)題了,但事實(shí)卻不是那么簡(jiǎn)單,因?yàn)槲覀儫o(wú)法知道某個(gè)編譯器具體是怎么做的,因?yàn)樵贘ava的memory model里對(duì)這個(gè)問(wèn)題沒(méi)有定義。 雙檢鎖對(duì)于基礎(chǔ)類型(比如int)適用。很顯然吧,因?yàn)榛A(chǔ)類型沒(méi)有調(diào)用構(gòu)造函數(shù)這一步。 |
|
來(lái)自: 精髓_感知力 > 《1603資料-Java》