單例模式講解以及Spring中的單例實(shí)現(xiàn)

單例模式講解以及Spring中的單例實(shí)現(xiàn)

最近在學(xué)習(xí)在spring源碼朵逝,一直都是云里霧里淳蔼,差一點(diǎn)就真的是從入門到放棄了稚虎,但是我不甘心呀我就開始思考撤嫩,如何看源碼更加容易,我想到一個(gè)解決方式就是看源碼首先需要站在大的格局觀來看其需要實(shí)現(xiàn)的功能蠢终,然后再Debug到每一行代碼序攘,這樣理解起來就會(huì)容易得多茴她。但是如何站在大的格局觀,看其需要實(shí)現(xiàn)的功能呢程奠?那么就不得不說設(shè)計(jì)模式了丈牢,Spring中也涉及到很多設(shè)計(jì)模式,所以在此把以前學(xué)習(xí)設(shè)計(jì)模式的東西都撿起來瞄沙,結(jié)合spring源碼學(xué)習(xí)一起來分享出來

國王只能有一個(gè)

首先一來己沛,我就要建立一個(gè)國家,首先從過往開始
可是這個(gè)時(shí)候我們需要思考一個(gè)現(xiàn)實(shí)問題距境,那就是國王只有一個(gè)申尼,那么我們?cè)诔绦虻脑O(shè)計(jì)中如何做到讓一個(gè)類(國王)的實(shí)例只能有一個(gè)呢?實(shí)現(xiàn)方式又很多種肮疗,讓我一一道來

我們java程序員都知道晶姊,對(duì)象都是靠new出來了,既然只能有一個(gè)國王伪货,那么我們控制這個(gè)new不就可以了嗎们衙?

餓漢模式

`

public class King {
    //在靜態(tài)變量初始化的時(shí)候就將對(duì)象創(chuàng)建出來了,
    private static King ourInstance = new King();

    public static King getInstance() {
        return ourInstance;
    }
    //將構(gòu)造方法私有化碱呼,如此一來蒙挑,外部就無法創(chuàng)建對(duì)象了
    private King() {
    }
}

`

到這里,單例的國王就創(chuàng)建完成了愚臀,好的忆蚀,我們來看看單例模式的定義

單例模式:確保一個(gè)類只有一個(gè)實(shí)例,并提供一個(gè)全局訪問點(diǎn)

上面創(chuàng)建國王的過程有一個(gè)特點(diǎn)姑裂,就是 在靜態(tài)變量初始化的時(shí)候就將國王實(shí)例化了馋袜,對(duì)于程序而言就是不論我們使不使用這個(gè)國王,他都把內(nèi)存給占用了舶斧,當(dāng)程序非常龐大的時(shí)候欣鳖,就非常消耗內(nèi)存,所以我們并不是非常推薦這么做茴厉。當(dāng)然這種單例模式是線程安全的泽台,我們稱之為餓漢模式

懶漢模式

有沒有一種方式,讓國王是被選舉出來的呢矾缓,就是當(dāng)程序調(diào)用getInstance()這個(gè)方法的時(shí)候才會(huì)給我們創(chuàng)建一個(gè)國王怀酷。這種形式的創(chuàng)建當(dāng)然有。其實(shí)也很簡單 稱之為懶漢模式

public class King {
    private static volatile King ourInstance ;

    public static King getInstance() {
        if (ourInstance==null) {
            ourInstance = new King();
        }
        return ourInstance;
    }
    private King() {
    }
}

注意:如果編寫的是多線程程序嗜闻,則不要?jiǎng)h除上例代碼中的關(guān)鍵字 volatile 和 synchronized蜕依,否則將存在線程非安全的問題。如果不刪除這兩個(gè)關(guān)鍵字就能保證線程安全,但是每次訪問時(shí)都要同步笔横,會(huì)影響性能竞滓,且消耗更多的資源,這是懶漢式單例的缺點(diǎn)吹缔。

可是這樣一做我們就返現(xiàn)商佑,當(dāng)一群人在選舉國王的時(shí)候,其他人是沒辦法選舉國王厢塘,在這種場(chǎng)景下似乎好像還說的過去茶没,但是對(duì)于一個(gè)程序而言的話,那么效率是非常低下了晚碾,并不推薦這樣操作抓半,那么還有沒有其他方式呢?有格嘁。我們用雙重判斷的方式就可以實(shí)現(xiàn)

public class King {
    private static King ourInstance ;

    public static King getInstance() {
        if (ourInstance==null) {
            synchronized (King.class){
                if (ourInstance == null) {
                    ourInstance = new King();
                }
            }
        }
        return ourInstance;
    }

    private King() {
    }
}

這樣一來既解決了效率方面的問題笛求,也解決了線程方面的問題,當(dāng)然單例模式可不僅僅只有這幾種糕簿,還有內(nèi)部類探入,枚舉等形式,在此就不過多的介紹了
單例說到這里懂诗,接下來我們就開始結(jié)合spring來分析spring中的單例是如何實(shí)現(xiàn)的

靜態(tài)內(nèi)部類可以實(shí)現(xiàn)單例模式的原因 :靜態(tài)內(nèi)部類使用的時(shí)候才會(huì)加載蜂嗽,且只加載一次

spring中的單例模式的應(yīng)用

接下來看看在spring中單例模式的應(yīng)用,

spring 中加載單例的過程都是在BeanFactory接口中定義的getBean(…)這個(gè)方法中定義的殃恒,實(shí)現(xiàn)默認(rèn)是在
AbstractBeanFactory中植旧,主要代碼功能兩點(diǎn)

  • 從緩存中獲取單例bean

  • 從bean的實(shí)例中獲取對(duì)象

    廢話不多說直接上代碼

    protected <T> T doGetBean(final String name, @Nullable final Class<T> requiredType,
              @Nullable final Object[] args, boolean typeCheckOnly) throws BeansException {
    
          //對(duì)傳入的beanName稍作修改,防止有一些非法字段,然后提取bean 的Name
          final String beanName = transformedBeanName(name);
          Object bean;
    
          // Eagerly check singleton cache for manually registered singletons.
          //直接從緩存中獲取單例工廠中的objectFactory獲取單例
          Object sharedInstance = getSingleton(beanName);
          if (sharedInstance != null && args == null) {
              if (logger.isDebugEnabled()) {
                  if (isSingletonCurrentlyInCreation(beanName)) {
                      logger.debug("Returning eagerly cached instance of singleton bean '" + beanName +
                              "' that is not fully initialized yet - a consequence of a circular reference");
                  }
                  else {
                      logger.debug("Returning cached instance of singleton bean '" + beanName + "'");
                  }
              }
              //返回對(duì)應(yīng)的實(shí)例离唐,從bean實(shí)例中獲取對(duì)象
              //有時(shí)候存在諸如BeanFactory的情況并不是直接返回實(shí)例本身而是返回指定方法返回的實(shí)例
              bean = getObjectForBeanInstance(sharedInstance, name, beanName, null);
          }
    
          else {
              //創(chuàng)建bean,解決了很多關(guān)于循環(huán)依賴等等很多的功能
    

    其實(shí)getBean()這個(gè)方法不僅僅和單例相關(guān)病附,還和prototype相關(guān),畢竟只是獲取Bean,Bean的scope也可能為prototype亥鬓,

/**
     * 單例對(duì)象的緩存
     */
private final Map<String, Object> singletonObjects = new ConcurrentHashMap<>(256);

protected Object getSingleton(String beanName, boolean allowEarlyReference) {
//      首先通過名字查找這個(gè)單例bean存在不存在
        Object singletonObject = this.singletonObjects.get(beanName);
        if (singletonObject == null && isSingletonCurrentlyInCreation(beanName)) {
            synchronized (this.singletonObjects) {
                //查看緩存中是否存在這個(gè)bean實(shí)例
                singletonObject = this.earlySingletonObjects.get(beanName);
                //如果這個(gè)時(shí)候的bean實(shí)例還是為空并且允許懶加載
                if (singletonObject == null && allowEarlyReference) {
                    ObjectFactory<?> singletonFactory = this.singletonFactories.get(beanName);
                    if (singletonFactory != null) {
                        singletonObject = singletonFactory.getObject();
                        this.earlySingletonObjects.put(beanName, singletonObject);
                        this.singletonFactories.remove(beanName);
                    }
                }
            }
        }
        return singletonObject;
    }

上面這段代碼中 synchronized (this.singletonObjects) 是關(guān)鍵完沪,但是前提條件
isSingletonCurrentlyInCreation返回值也是為true,也就是這個(gè)單例Bean正在創(chuàng)建,所以基本上第一次調(diào)用doGetBean的時(shí)候上面這個(gè)getSingleton基本上都是返回的null,所以進(jìn)行doGetBean就接著往下運(yùn)行

protected <T> T doGetBean(final String name, @Nullable final Class<T> requiredType,
            @Nullable final Object[] args, boolean typeCheckOnly) throws BeansException {
            ....
            //這個(gè)前面省略掉部分代碼
            獲取 beanDefinition
                final RootBeanDefinition mbd = getMergedLocalBeanDefinition(beanName);
                checkMergedBeanDefinition(mbd, beanName, args);

                // Guarantee initialization of beans that the current bean depends on.
                String[] dependsOn = mbd.getDependsOn();
                if (dependsOn != null) {
                    for (String dep : dependsOn) {
                        if (isDependent(beanName, dep)) {
                            throw new BeanCreationException(mbd.getResourceDescription(), beanName,
                                    "Circular depends-on relationship between '" + beanName + "' and '" + dep + "'");
                        }
                        registerDependentBean(dep, beanName);
                        try {
                            getBean(dep);
                        }
                        catch (NoSuchBeanDefinitionException ex) {
                            throw new BeanCreationException(mbd.getResourceDescription(), beanName,
                                    "'" + beanName + "' depends on missing bean '" + dep + "'", ex);
                        }
                    }
                }
            if (mbd.isSingleton()) {
                    sharedInstance = getSingleton(beanName, () -> {
                        try {
                            return createBean(beanName, mbd, args);
                        }
                        catch (BeansException ex) {
                            // Explicitly remove instance from singleton cache: It might have been put there
                            // eagerly by the creation process, to allow for circular reference resolution.
                            // Also remove any beans that received a temporary reference to the bean.
                            destroySingleton(beanName);
                            throw ex;
                        }
                    });
                    bean = getObjectForBeanInstance(sharedInstance, name, beanName, mbd);
                }
        }

首先 BeanFactory中已經(jīng)存儲(chǔ)了我們從xml解析出來的相關(guān)信息放在BeanDefinitionMap(作用后期會(huì)講)中贮竟,然后通過這個(gè)map獲取到RootBeanDefinition(功能后期也會(huì)講,在此理解為一個(gè)有屬性值较剃,構(gòu)造方法和其他相關(guān)信息的Bean ) 咕别,然后就有個(gè)判斷if (mbd.isSingleton()) 如果是單例的就接著getSingleton的重載方法,傳入的是mbd,

當(dāng)從緩存中加載單例對(duì)象的時(shí)候singletonObjects這個(gè)map(用來存放緩存的單例)写穴,并且只要?jiǎng)?chuàng)建一個(gè)單例對(duì)象就會(huì)把當(dāng)前的單例對(duì)象存放在這個(gè)singletonObjects中惰拱,這樣一來就保證了在getBean的時(shí)候這里面永遠(yuǎn)就只有一個(gè),實(shí)現(xiàn)了我們?cè)讷@取某個(gè)對(duì)象過得時(shí)候才會(huì)給她分配內(nèi)存。既保證了內(nèi)存高效利用偿短,又是線程安全的

public Object getSingleton(String beanName, ObjectFactory<?> singletonFactory) {
        Assert.notNull(beanName, "Bean name must not be null");
        synchronized (this.singletonObjects) {
//          直接從緩存中獲取單例bean
            Object singletonObject = this.singletonObjects.get(beanName);
            if (singletonObject == null) {
                if (this.singletonsCurrentlyInDestruction) {
                    throw new BeanCreationNotAllowedException(beanName,
                            "Singleton bean creation not allowed while singletons of this factory are in destruction " +
                            "(Do not request a bean from a BeanFactory in a destroy method implementation!)");
                }
                if (logger.isDebugEnabled()) {
                    logger.debug("Creating shared instance of singleton bean '" + beanName + "'");
                }
                beforeSingletonCreation(beanName);
                boolean newSingleton = false;
                boolean recordSuppressedExceptions = (this.suppressedExceptions == null);
                if (recordSuppressedExceptions) {
                    this.suppressedExceptions = new LinkedHashSet<>();
                }
                try {
                    singletonObject = singletonFactory.getObject();
                    newSingleton = true;
                }
                catch (IllegalStateException ex) {
                    
                    singletonObject = this.singletonObjects.get(beanName);
                    if (singletonObject == null) {
                        throw ex;
                    }
                }
                catch (BeanCreationException ex) {
                    if (recordSuppressedExceptions) {
                        for (Exception suppressedException : this.suppressedExceptions) {
                            ex.addRelatedCause(suppressedException);
                        }
                    }
                    throw ex;
                }
                finally {
                    if (recordSuppressedExceptions) {
                        this.suppressedExceptions = null;
                    }
                    afterSingletonCreation(beanName);
                }
                if (newSingleton) {
                    //在singletonObject中添加我們想要加載的單例
                    addSingleton(beanName, singletonObject);
                }
            }
            return singletonObject;
        }
    }

這樣的話欣孤,我們就從實(shí)例中也獲取到了Bean,也創(chuàng)建單例Bean的緩存,當(dāng)下一次還需要這個(gè)單例Bean的時(shí)候就直接從緩存中獲取了昔逗,
最后一點(diǎn)總結(jié):
Spring中創(chuàng)建單例的過程真的是非常的繞降传,但是邏輯還是非常清楚的,就是將我們需要的對(duì)象放在map中勾怒,下次需要的時(shí)候就直接從map中獲取婆排,只是spring在處理的時(shí)候,需要解決其他很多問題而已笔链,到此單例模式就真的結(jié)束了

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末段只,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子鉴扫,更是在濱河造成了極大的恐慌赞枕,老刑警劉巖,帶你破解...
    沈念sama閱讀 218,546評(píng)論 6 507
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件坪创,死亡現(xiàn)場(chǎng)離奇詭異炕婶,居然都是意外死亡,警方通過查閱死者的電腦和手機(jī)误堡,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,224評(píng)論 3 395
  • 文/潘曉璐 我一進(jìn)店門古话,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人锁施,你說我怎么就攤上這事陪踩。” “怎么了悉抵?”我有些...
    開封第一講書人閱讀 164,911評(píng)論 0 354
  • 文/不壞的土叔 我叫張陵肩狂,是天一觀的道長。 經(jīng)常有香客問我姥饰,道長傻谁,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 58,737評(píng)論 1 294
  • 正文 為了忘掉前任列粪,我火速辦了婚禮审磁,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘岂座。我一直安慰自己态蒂,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,753評(píng)論 6 392
  • 文/花漫 我一把揭開白布费什。 她就那樣靜靜地躺著钾恢,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上瘩蚪,一...
    開封第一講書人閱讀 51,598評(píng)論 1 305
  • 那天泉懦,我揣著相機(jī)與錄音,去河邊找鬼疹瘦。 笑死崩哩,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的拱礁。 我是一名探鬼主播琢锋,決...
    沈念sama閱讀 40,338評(píng)論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場(chǎng)噩夢(mèng)啊……” “哼呢灶!你這毒婦竟也來了吴超?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 39,249評(píng)論 0 276
  • 序言:老撾萬榮一對(duì)情侶失蹤鸯乃,失蹤者是張志新(化名)和其女友劉穎鲸阻,沒想到半個(gè)月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體缨睡,經(jīng)...
    沈念sama閱讀 45,696評(píng)論 1 314
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡鸟悴,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,888評(píng)論 3 336
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了奖年。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片细诸。...
    茶點(diǎn)故事閱讀 40,013評(píng)論 1 348
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖陋守,靈堂內(nèi)的尸體忽然破棺而出震贵,到底是詐尸還是另有隱情,我是刑警寧澤水评,帶...
    沈念sama閱讀 35,731評(píng)論 5 346
  • 正文 年R本政府宣布猩系,位于F島的核電站,受9級(jí)特大地震影響中燥,放射性物質(zhì)發(fā)生泄漏寇甸。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,348評(píng)論 3 330
  • 文/蒙蒙 一疗涉、第九天 我趴在偏房一處隱蔽的房頂上張望拿霉。 院中可真熱鬧,春花似錦咱扣、人聲如沸绽淘。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,929評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽收恢。三九已至,卻和暖如春祭往,著一層夾襖步出監(jiān)牢的瞬間伦意,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 33,048評(píng)論 1 270
  • 我被黑心中介騙來泰國打工硼补, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留驮肉,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 48,203評(píng)論 3 370
  • 正文 我出身青樓已骇,卻偏偏與公主長得像离钝,于是被迫代替她去往敵國和親。 傳聞我的和親對(duì)象是個(gè)殘疾皇子褪储,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,960評(píng)論 2 355