定義:
單例模式(singleton),保證一個(gè)類僅有一個(gè)實(shí)例,并且提供一個(gè)訪問它的全局訪問點(diǎn)。 這句話很好理解,今天我們的重點(diǎn)也不在于如何解讀單例模式。
在面試的過程中,往往會(huì)遇到考察手寫單例模式的場(chǎng)景,今天讓我們關(guān)注一下,寫單例模式的幾種方法。
餓漢式:
/** * 餓漢式. * * @author jialin.li * @date 2019-12-30 22:13 */ public class
Singleton {private Singleton() { } private static Singleton singleton = new
Singleton();public Singleton getInstance(){ return singleton; } }
*
* 餓漢式的特點(diǎn)是類初始化的時(shí)候,創(chuàng)建了該對(duì)象。
* 由于類只會(huì)初始化一次,所以保證了對(duì)象只會(huì)被創(chuàng)建一次。
* 同時(shí)將構(gòu)造方法私有化,保證了沒有辦法從外部創(chuàng)建對(duì)象。 這種方法的問題是,如果該實(shí)例從始至終都沒有被使用過,就會(huì)造成內(nèi)存的浪費(fèi)。
懶漢式:
/** * 懶漢式. * * @author jialin.li * @date 2019-12-30 22:13 */ public class
Singleton {private Singleton() { } private volatile Singleton singleton = null;
public Singleton getInstance(){ // 提高性能,降低線程進(jìn)入臨界區(qū)的可能 if(singleton == null){
synchronized (Singleton.class){ if(singleton == null){ singleton = new
Singleton(); } } }return singleton; } } 這種寫法又被成為雙檢鎖模式,是一種實(shí)現(xiàn)單例模式的經(jīng)典寫法。
代碼中有兩處判空:
*
* 第一處判空,是為了提高性能,降低線程進(jìn)入臨界區(qū)的可能性。
*
第二處判空是為了線程同步,假如沒有第二處判空,則可能兩個(gè)線程都通過了if(singleton==null)條件,這樣即使是臨界區(qū)內(nèi)只有一個(gè)線程在執(zhí)行,臨界區(qū)內(nèi)的代碼也會(huì)被執(zhí)行兩遍,這樣就會(huì)產(chǎn)生兩個(gè)對(duì)象,不符合單例模式。
成員變量使用了volatile進(jìn)行修飾,一方面是保證了對(duì)象在多線程環(huán)境下的可見性,另一方面是為了防止new
Singleton()進(jìn)行指令重排序而導(dǎo)致的并發(fā)問題。 volatile關(guān)鍵字的作用兩個(gè):
*
* 保證變量在線程之間的可見性(直接從主存中讀寫數(shù)據(jù),不經(jīng)過工作內(nèi)存)
* 阻止編譯時(shí)和運(yùn)行時(shí)的指令重排,編譯時(shí)JVM編譯器遵循內(nèi)存屏障約束,運(yùn)行時(shí)依賴CPU屏障來(lái)阻止指令重排。
指令重排是指JVM在編譯Java代碼的時(shí)候,或者CPU在執(zhí)行JVM字節(jié)碼的時(shí)候,對(duì)現(xiàn)有的指令順序進(jìn)行重新排序。
指令重排的目的是為了在不改變程序執(zhí)行結(jié)果的前提下,優(yōu)化程序的運(yùn)行效率。需要注意的是,這里所說的不改變執(zhí)行結(jié)果,指的是不改變單線程下的程序執(zhí)行結(jié)果。
這里不太好懂,舉一個(gè)例子,正常的new Singleton()創(chuàng)建步驟是:
* 開辟一塊內(nèi)存空間
* 創(chuàng)建對(duì)象
* 將對(duì)象的地址存入引用變量 經(jīng)過指令重排后,可能變成了:
* 開辟一塊內(nèi)存空間
* 將對(duì)象的地址存入引用變量
* 創(chuàng)建對(duì)象
假設(shè)發(fā)生了指令重排,線程A、B都執(zhí)行這段代碼,線程A執(zhí)行到了new
Singleton()的步驟2,此時(shí)還沒有創(chuàng)建對(duì)象,這個(gè)時(shí)候發(fā)生了線程的切換。線程B開始執(zhí)行,這個(gè)時(shí)候線程B還可以通過if(singleton ==
null)的判斷,因?yàn)榫€程A中的singleton只是指向了一個(gè)空的內(nèi)存地址,這個(gè)時(shí)候線程B創(chuàng)建出了一個(gè)Singleton對(duì)象,當(dāng)線程切換成A時(shí),線程A仍執(zhí)行了new
Singleton()的步驟3,此時(shí)創(chuàng)建了2個(gè)Singleton對(duì)象,不符合單例模式。
靜態(tài)內(nèi)部類單例模式:
/** * 靜態(tài)內(nèi)部類單例模式. * * @author jialin.li * @date 2019-12-30 22:13 */ public class
Singleton {private Singleton() { } public static Singleton getInstance() {
return Inner.singleton; } private static class Inner { private static Singleton
singleton =new Singleton(); } }
這里利用的是內(nèi)部類的特性,只有第一次調(diào)用getInstance方法的時(shí)候,虛擬機(jī)才會(huì)加載Inner并初始化singleton,并且只初始化一次。這種方法也是一種懶漢式的寫法,只有在需要的時(shí)候,才創(chuàng)建對(duì)象。
枚舉單例模式
枚舉類也是一種單例模式 public enum Singleton { INSTANCE //doSomething 該實(shí)例支持的行為 //
可以省略此方法,通過Singleton.INSTANCE進(jìn)行操作 public static Singleton get Instance() { return
Singleton.INSTANCE; } } 這種寫法較為簡(jiǎn)單,并且沒有辦法用反射的方式,創(chuàng)建對(duì)象。但是可讀性較差。
期待您的關(guān)注、推薦、收藏,同時(shí)也期待您的糾錯(cuò)和批評(píng)。
熱門工具 換一換