<kbd id="afajh"><form id="afajh"></form></kbd>
<strong id="afajh"><dl id="afajh"></dl></strong>
    <del id="afajh"><form id="afajh"></form></del>
        1. <th id="afajh"><progress id="afajh"></progress></th>
          <b id="afajh"><abbr id="afajh"></abbr></b>
          <th id="afajh"><progress id="afajh"></progress></th>

          單例模式,真不簡單

          共 13410字,需瀏覽 27分鐘

           ·

          2021-10-23 03:02

          前言

          單例模式無論在我們面試,還是日常工作中,都會面對的問題。但很多單例模式的細(xì)節(jié),值得我們深入探索一下。

          這篇文章透過單例模式,串聯(lián)了多方面基礎(chǔ)知識,非常值得一讀。

          1 什么是單例模式?

          單例模式是一種非常常用的軟件設(shè)計模式,它定義是單例對象的類只能允許一個實例存在

          該類負(fù)責(zé)創(chuàng)建自己的對象,同時確保只有一個對象被創(chuàng)建。一般常用在工具類的實現(xiàn)或創(chuàng)建對象需要消耗資源的業(yè)務(wù)場景。

          單例模式的特點:

          • 類構(gòu)造器私有
          • 持有自己類的引用
          • 對外提供獲取實例的靜態(tài)方法

          我們先用一個簡單示例了解一下單例模式的用法。

          public?class?SimpleSingleton?{
          ????//持有自己類的引用
          ????private?static?final?SimpleSingleton?INSTANCE?=?new?SimpleSingleton();

          ????//私有的構(gòu)造方法
          ????private?SimpleSingleton()?{
          ????}
          ????//對外提供獲取實例的靜態(tài)方法
          ????public?static?SimpleSingleton?getInstance()?{
          ????????return?INSTANCE;
          ????}
          ????
          ????public?static?void?main(String[]?args)?{
          ????????System.out.println(SimpleSingleton.getInstance().hashCode());
          ????????System.out.println(SimpleSingleton.getInstance().hashCode());
          ????}
          }

          打印結(jié)果:

          1639705018
          1639705018

          我們看到兩次獲取SimpleSingleton實例的hashCode是一樣的,說明兩次調(diào)用獲取到的是同一個對象。

          可能很多朋友平時工作當(dāng)中都是這么用的,但我要說這段代碼是有問題的,你會相信嗎?

          不信,我們一起往下看。

          2 餓漢和懶漢模式

          在介紹單例模式的時候,必須要先介紹它的兩種非常著名的實現(xiàn)方式:餓漢模式懶漢模式

          2.1 餓漢模式

          實例在初始化的時候就已經(jīng)建好了,不管你有沒有用到,先建好了再說。具體代碼如下:

          public?class?SimpleSingleton?{
          ????//持有自己類的引用
          ????private?static?final?SimpleSingleton?INSTANCE?=?new?SimpleSingleton();

          ????//私有的構(gòu)造方法
          ????private?SimpleSingleton()?{
          ????}
          ????//對外提供獲取實例的靜態(tài)方法
          ????public?static?SimpleSingleton?getInstance()?{
          ????????return?INSTANCE;
          ????}
          }

          餓漢模式,其實還有一個變種:

          public?class?SimpleSingleton?{
          ????//持有自己類的引用
          ????private?static?final?SimpleSingleton?INSTANCE;
          ????static?{
          ???????INSTANCE?=?new?SimpleSingleton();
          ????}

          ????//私有的構(gòu)造方法
          ????private?SimpleSingleton()?{
          ????}
          ????//對外提供獲取實例的靜態(tài)方法
          ????public?static?SimpleSingleton?getInstance()?{
          ????????return?INSTANCE;
          ????}
          }

          使用靜態(tài)代碼塊的方式實例化INSTANCE對象。

          使用餓漢模式的好處是:沒有線程安全的問題,但帶來的壞處也很明顯。

          private?static?final?SimpleSingleton?INSTANCE?=?new?SimpleSingleton();

          一開始就實例化對象了,如果實例化過程非常耗時,并且最后這個對象沒有被使用,不是白白造成資源浪費(fèi)嗎?

          還真是啊。

          這個時候你也許會想到,不用提前實例化對象,在真正使用的時候再實例化不就可以了?

          這就是我接下來要介紹的:懶漢模式

          2.2 懶漢模式

          顧名思義就是實例在用到的時候才去創(chuàng)建,“比較懶”,用的時候才去檢查有沒有實例,如果有則返回,沒有則新建。具體代碼如下:

          public?class?SimpleSingleton2?{

          ????private?static?SimpleSingleton2?INSTANCE;

          ????private?SimpleSingleton2()?{
          ????}

          ????public?static?SimpleSingleton2?getInstance()?{
          ????????if?(INSTANCE?==?null)?{
          ????????????INSTANCE?=?new?SimpleSingleton2();
          ????????}
          ????????return?INSTANCE;
          ????}
          }

          示例中的INSTANCE對象一開始是空的,在調(diào)用getInstance方法才會真正實例化。

          嗯,不錯不錯。但這段代碼還是有問題。

          2.3 synchronized關(guān)鍵字

          上面的代碼有什么問題?

          答:假如有多個線程中都調(diào)用了getInstance方法,那么都走到 if (INSTANCE == null) 判斷時,可能同時成立,因為INSTANCE初始化時默認(rèn)值是null。這樣會導(dǎo)致多個線程中同時創(chuàng)建INSTANCE對象,即INSTANCE對象被創(chuàng)建了多次,違背了只創(chuàng)建一個INSTANCE對象的初衷。

          那么,要如何改進(jìn)呢?

          答:最簡單的辦法就是使用synchronized關(guān)鍵字。

          改進(jìn)后的代碼如下:

          public?class?SimpleSingleton3?{
          ????private?static?SimpleSingleton3?INSTANCE;

          ????private?SimpleSingleton3()?{
          ????}

          ????public?synchronized?static?SimpleSingleton3?getInstance()?{
          ????????if?(INSTANCE?==?null)?{
          ????????????INSTANCE?=?new?SimpleSingleton3();
          ????????}
          ????????return?INSTANCE;
          ????}
          ????public?static?void?main(String[]?args)?{
          ????????System.out.println(SimpleSingleton3.getInstance().hashCode());
          ????????System.out.println(SimpleSingleton3.getInstance().hashCode());
          ????}
          }

          在getInstance方法上加synchronized關(guān)鍵字,保證在并發(fā)的情況下,只有一個線程能創(chuàng)建INSTANCE對象的實例。

          這樣總可以了吧?

          答:不好意思,還是有問題。

          有什么問題?

          答:使用synchronized關(guān)鍵字會消耗getInstance方法的性能,我們應(yīng)該判斷當(dāng)INSTANCE為空時才加鎖,如果不為空不應(yīng)該加鎖,需要直接返回。

          這就需要使用下面要說的雙重檢查鎖了。

          2.4 餓漢和懶漢模式的區(qū)別

          but,在介紹雙重檢查鎖之前,先插播一個朋友們可能比較關(guān)心的話題:餓漢模式 和 懶漢模式 各有什么優(yōu)缺點?

          • 餓漢模式:優(yōu)點是沒有線程安全的問題,缺點是浪費(fèi)內(nèi)存空間。
          • 懶漢模式:優(yōu)點是沒有內(nèi)存空間浪費(fèi)的問題,缺點是如果控制不好,實際上不是單例的。

          好了,下面可以安心的看看雙重檢查鎖,是如何保證性能的,同時又保證單例的。

          3 雙重檢查鎖

          雙重檢查鎖顧名思義會檢查兩次:在加鎖之前檢查一次是否為空,加鎖之后再檢查一次是否為空。

          那么,它是如何實現(xiàn)單例的呢?

          3.1 如何實現(xiàn)單例?

          具體代碼如下:

          public?class?SimpleSingleton4?{

          ????private?static?SimpleSingleton4?INSTANCE;

          ????private?SimpleSingleton4()?{
          ????}

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

          在加鎖之前判斷是否為空,可以確保INSTANCE不為空的情況下,不用加鎖,可以直接返回。

          為什么在加鎖之后,還需要判斷INSTANCE是否為空呢?

          答:是為了防止在多線程并發(fā)的情況下,只會實例化一個對象。

          比如:線程a和線程b同時調(diào)用getInstance方法,假如同時判斷INSTANCE都為空,這時會同時進(jìn)行搶鎖。

          假如線程a先搶到鎖,開始執(zhí)行synchronized關(guān)鍵字包含的代碼,此時線程b處于等待狀態(tài)。

          線程a創(chuàng)建完新實例了,釋放鎖了,此時線程b拿到鎖,進(jìn)入synchronized關(guān)鍵字包含的代碼,如果沒有再判斷一次INSTANCE是否為空,則可能會重復(fù)創(chuàng)建實例。

          所以需要在synchronized前后兩次判斷。

          不要以為這樣就完了,還有問題呢?

          3.2 volatile關(guān)鍵字

          上面的代碼還有啥問題?

          public?static?SimpleSingleton4?getInstance()?{
          ??????if?(INSTANCE?==?null)?{//1
          ??????????synchronized?(SimpleSingleton4.class)?{//2
          ??????????????if?(INSTANCE?==?null)?{//3
          ??????????????????INSTANCE?=?new?SimpleSingleton4();//4
          ??????????????}
          ??????????}
          ??????}
          ??????return?INSTANCE;//5
          ??}

          getInstance方法的這段代碼,我是按1、2、3、4、5這種順序?qū)懙模M舶催@個順序執(zhí)行。

          但是java虛擬機(jī)實際上會做一些優(yōu)化,對一些代碼指令進(jìn)行重排。重排之后的順序可能就變成了:1、3、2、4、5,這樣在多線程的情況下同樣會創(chuàng)建多次實例。重排之后的代碼可能如下:

          public?static?SimpleSingleton4?getInstance()?{
          ????if?(INSTANCE?==?null)?{//1
          ???????if?(INSTANCE?==?null)?{//3
          ???????????synchronized?(SimpleSingleton4.class)?{//2
          ????????????????INSTANCE?=?new?SimpleSingleton4();//4
          ????????????}
          ????????}
          ????}
          ????return?INSTANCE;//5
          }

          原來如此,那有什么辦法可以解決呢?

          答:可以在定義INSTANCE是加上volatile關(guān)鍵字。具體代碼如下:

          public?class?SimpleSingleton7?{

          ????private?volatile?static?SimpleSingleton7?INSTANCE;

          ????private?SimpleSingleton7()?{
          ????}

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

          volatile關(guān)鍵字可以保證多個線程的可見性,但是不能保證原子性。同時它也能禁止指令重排。

          雙重檢查鎖的機(jī)制既保證了線程安全,又比直接上鎖提高了執(zhí)行效率,還節(jié)省了內(nèi)存空間。

          除了上面的單例模式之外,還有沒有其他的單例模式?

          4 靜態(tài)內(nèi)部類

          靜態(tài)內(nèi)部類顧名思義是通過靜態(tài)的內(nèi)部類來實現(xiàn)單例模式的。

          那么,它是如何實現(xiàn)單例的呢?

          4.1 如何實現(xiàn)單例模式?

          具體代碼如下:

          public?class?SimpleSingleton5?{

          ????private?SimpleSingleton5()?{
          ????}

          ????public?static?SimpleSingleton5?getInstance()?{
          ????????return?Inner.INSTANCE;
          ????}

          ????private?static?class?Inner?{
          ????????private?static?final?SimpleSingleton5?INSTANCE?=?new?SimpleSingleton5();
          ????}
          }

          我們看到在SimpleSingleton5類中定義了一個靜態(tài)的內(nèi)部類Inner。在SimpleSingleton5類的getInstance方法中,返回的是內(nèi)部類Inner的實例INSTANCE對象。

          只有在程序第一次調(diào)用getInstance方法時,虛擬機(jī)才加載Inner并實例化INSTANCE對象。

          java內(nèi)部機(jī)制保證了,只有一個線程可以獲得對象鎖,其他的線程必須等待,保證對象的唯一性。

          4.2 反射漏洞

          上面的代碼看似完美,但還是有漏洞。如果其他人使用反射,依然能夠通過類的無參構(gòu)造方式創(chuàng)建對象。例如:

          Class?simpleSingleton5Class?=?SimpleSingleton5.class;
          try?{
          ????SimpleSingleton5?newInstance?=?simpleSingleton5Class.newInstance();
          ????System.out.println(newInstance?==?SimpleSingleton5.getInstance());
          }?catch?(InstantiationException?e)?{
          ????e.printStackTrace();
          }?catch?(IllegalAccessException?e)?{
          ????e.printStackTrace();
          }

          上面代碼打印結(jié)果是false。

          由此看出,通過反射創(chuàng)建的對象,跟通過getInstance方法獲取的對象,并非同一個對象,也就是說,這個漏洞會導(dǎo)致SimpleSingleton5非單例。

          那么,要如何防止這個漏洞呢?

          答:這就需要在無參構(gòu)造方式中判斷,如果非空,則拋出異常了。

          改造后的代碼如下:

          public?class?SimpleSingleton5?{

          ????private?SimpleSingleton5()?{
          ????????if(Inner.INSTANCE?!=?null)?{
          ???????????throw?new?RuntimeException("不能支持重復(fù)實例化");
          ???????}
          ????}

          ????public?static?SimpleSingleton5?getInstance()?{
          ????????return?Inner.INSTANCE;
          ????}

          ????private?static?class?Inner?{
          ????????private?static?final?SimpleSingleton5?INSTANCE?=?new?SimpleSingleton5();
          ????????}
          ????}

          }

          如果此時,你認(rèn)為這種靜態(tài)內(nèi)部類,實現(xiàn)單例模式的方法,已經(jīng)完美了。

          那么,我要告訴你的是,你錯了,還有漏洞。。。

          4.3 反序列化漏洞

          眾所周知,java中的類通過實現(xiàn)Serializable接口,可以實現(xiàn)序列化。

          我們可以把類的對象先保存到內(nèi)存,或者某個文件當(dāng)中。后面在某個時刻,再恢復(fù)成原始對象。

          具體代碼如下:

          public?class?SimpleSingleton5?implements?Serializable?{

          ????private?SimpleSingleton5()?{
          ????????if?(Inner.INSTANCE?!=?null)?{
          ????????????throw?new?RuntimeException("不能支持重復(fù)實例化");
          ????????}
          ????}

          ????public?static?SimpleSingleton5?getInstance()?{
          ????????return?Inner.INSTANCE;
          ????}

          ????private?static?class?Inner?{
          ????????private?static?final?SimpleSingleton5?INSTANCE?=?new?SimpleSingleton5();
          ????}

          ????private?static?void?writeFile()?{
          ????????FileOutputStream?fos?=?null;
          ????????ObjectOutputStream?oos?=?null;
          ????????try?{
          ????????????SimpleSingleton5?simpleSingleton5?=?SimpleSingleton5.getInstance();
          ????????????fos?=?new?FileOutputStream(new?File("test.txt"));
          ????????????oos?=?new?ObjectOutputStream(fos);
          ????????????oos.writeObject(simpleSingleton5);
          ????????????System.out.println(simpleSingleton5.hashCode());
          ????????}?catch?(FileNotFoundException?e)?{
          ????????????e.printStackTrace();
          ????????}?catch?(IOException?e)?{
          ????????????e.printStackTrace();
          ????????}?finally?{
          ????????????if?(oos?!=?null)?{
          ????????????????try?{
          ????????????????????oos.close();
          ????????????????}?catch?(IOException?e)?{
          ????????????????????e.printStackTrace();
          ????????????????}
          ????????????}
          ????????????if?(fos?!=?null)?{
          ????????????????try?{
          ????????????????????fos.close();
          ????????????????}?catch?(IOException?e)?{
          ????????????????????e.printStackTrace();
          ????????????????}
          ????????????}

          ????????}
          ????}

          ????private?static?void?readFile()?{
          ????????FileInputStream?fis?=?null;
          ????????ObjectInputStream?ois?=?null;
          ????????try?{
          ????????????fis?=?new?FileInputStream(new?File("test.txt"));
          ????????????ois?=?new?ObjectInputStream(fis);
          ????????????SimpleSingleton5?myObject?=?(SimpleSingleton5)?ois.readObject();

          ????????????System.out.println(myObject.hashCode());
          ????????}?catch?(FileNotFoundException?e)?{
          ????????????e.printStackTrace();
          ????????}?catch?(IOException?e)?{
          ????????????e.printStackTrace();
          ????????}?catch?(ClassNotFoundException?e)?{
          ????????????e.printStackTrace();
          ????????}?finally?{
          ????????????if?(ois?!=?null)?{
          ????????????????try?{
          ????????????????????ois.close();
          ????????????????}?catch?(IOException?e)?{
          ????????????????????e.printStackTrace();
          ????????????????}
          ????????????}
          ????????????if?(fis?!=?null)?{
          ????????????????try?{
          ????????????????????fis.close();
          ????????????????}?catch?(IOException?e)?{
          ????????????????????e.printStackTrace();
          ????????????????}
          ????????????}
          ????????}
          ????}

          ????public?static?void?main(String[]?args)?{
          ????????writeFile();
          ????????readFile();
          ????}
          }

          運(yùn)行之后,發(fā)現(xiàn)序列化和反序列化后對象的hashCode不一樣:

          189568618
          793589513

          說明,反序列化時創(chuàng)建了一個新對象,打破了單例模式對象唯一性的要求。

          那么,如何解決這個問題呢?

          答:重新readResolve方法。

          在上面的實例中,增加如下代碼:

          private?Object?readResolve()?throws?ObjectStreamException?{
          ????return?Inner.INSTANCE;
          }

          運(yùn)行結(jié)果如下:

          290658609
          290658609

          我們看到序列化和反序列化實例對象的hashCode相同了。

          做法很簡單,只需要在readResolve方法中,每次都返回唯一的Inner.INSTANCE對象即可。

          程序在反序列化獲取對象時,會去尋找readResolve()方法。

          • 如果該方法不存在,則直接返回新對象。
          • 如果該方法存在,則按該方法的內(nèi)容返回對象。
          • 如果我們之前沒有實例化單例對象,則會返回null。

          好了,到這來終于把坑都踩完了。

          還是費(fèi)了不少勁。

          不過,我偷偷告訴你一句,其實還有更簡單的方法,哈哈哈。

          納尼。。。

          5 枚舉

          其實在java中枚舉就是天然的單例,每一個實例只有一個對象,這是java底層內(nèi)部機(jī)制保證的。

          簡單的用法:

          public?enum??SimpleSingleton7?{
          ????INSTANCE;
          ????
          ????public?void?doSamething()?{
          ????????System.out.println("doSamething");
          ????}
          }???

          在調(diào)用的地方:

          public?class?SimpleSingleton7Test?{

          ????public?static?void?main(String[]?args)?{
          ????????SimpleSingleton7.INSTANCE.doSamething();
          ????}
          }

          在枚舉中實例對象INSTANCE是唯一的,所以它是天然的單例模式。

          當(dāng)然,在枚舉對象唯一性的這個特性,還能創(chuàng)建其他的單例對象,例如:

          public?enum??SimpleSingleton7?{
          ????INSTANCE;
          ????
          ????private?Student?instance;
          ????
          ????SimpleSingleton7()?{
          ???????instance?=?new?Student();
          ????}
          ????
          ????public?Student?getInstance()?{
          ???????return?instance;
          ????}
          }

          class?Student?{
          }

          jvm保證了枚舉是天然的單例,并且不存在線程安全問題,此外,還支持序列化。

          在java大神Joshua Bloch的經(jīng)典書籍《Effective Java》中說過:

          單元素的枚舉類型已經(jīng)成為實現(xiàn)Singleton的最佳方法。

          6 多例模式

          我們之前聊過的單例模式,都只會產(chǎn)生一個實例。但它其實還有一個變種,也就是我們接下來要聊的:多例模式

          多例模式顧名思義,它允許創(chuàng)建多個實例。但它的初衷是為了控制實例的個數(shù),其他的跟單例模式差不多。

          具體實現(xiàn)代碼如下:

          public?class?SimpleMultiPattern?{
          ????//持有自己類的引用
          ????private?static?final?SimpleMultiPattern?INSTANCE1?=?new?SimpleMultiPattern();
          ????private?static?final?SimpleMultiPattern?INSTANCE2?=?new?SimpleMultiPattern();

          ????//私有的構(gòu)造方法
          ????private?SimpleMultiPattern()?{
          ????}
          ????//對外提供獲取實例的靜態(tài)方法
          ????public?static?SimpleMultiPattern?getInstance(int?type)?{
          ????????if(type?==?1)?{
          ??????????return?INSTANCE1;
          ????????}
          ????????return?INSTANCE2;
          ????}
          }

          為了看起來更直觀,我把一些額外的安全相關(guān)代碼去掉了。

          有些朋友可能會說:既然多例模式也是為了控制實例數(shù)量,那我們常見的池技術(shù),比如:數(shù)據(jù)庫連接池,是不是通過多例模式實現(xiàn)的?

          答:不,它是通過享元模式實現(xiàn)的。

          那么,多例模式和享元模式有什么區(qū)別?

          • 多例模式:跟單例模式一樣,純粹是為了控制實例數(shù)量,使用這種模式的類,通常是作為程序某個模塊的入口。
          • 享元模式:它的側(cè)重點是對象之間的銜接。它把動態(tài)的、會變化的狀態(tài)剝離出來,共享不變的東西。

          7 真實使用場景

          最后,跟大家一起聊聊,單例模式的一些使用場景。我們主要看看在java的框架中,是如何使用單例模式,給有需要的朋友一個參考。

          7.1 Runtime

          jdk提供了Runtime類,我們可以通過這個類獲取系統(tǒng)的運(yùn)行狀態(tài)。

          比如可以通過它獲取cpu核數(shù):

          int?availableProcessors?=?Runtime.getRuntime().availableProcessors();

          Runtime類的關(guān)鍵代碼如下:

          public?class?Runtime?{
          ????private?static?Runtime?currentRuntime?=?new?Runtime();
          ????
          ????public?static?Runtime?getRuntime()?{
          ????????return?currentRuntime;
          ????}

          ????private?Runtime()?{}
          ????...
          }

          從上面的代碼我們可以看出,這是一個單例模式,并且是餓漢模式。

          但根據(jù)文章之前講過的一些理論知識,你會發(fā)現(xiàn)Runtime類的這種單例模式實現(xiàn)方式,顯然不太好。實例對象既沒用final關(guān)鍵字修飾,也沒考慮對象實例化的性能消耗問題。

          不過它的優(yōu)點是實現(xiàn)起來非常簡單。

          7.2 NamespaceHandlerResolver

          spring提供的DefaultNamespaceHandlerResolver是為需要初始化默認(rèn)命名空間處理器,是為了方便后面做標(biāo)簽解析用的。

          它的關(guān)鍵代碼如下:

          @Nullable
          private?volatile?Map?handlerMappings;

          private?Map?getHandlerMappings()?{
          ??Map?handlerMappings?=?this.handlerMappings;
          ??if?(handlerMappings?==?null)?{
          ???synchronized?(this)?{
          ????handlerMappings?=?this.handlerMappings;
          ????if?(handlerMappings?==?null)?{
          ?????if?(logger.isDebugEnabled())?{
          ??????logger.debug("Loading?NamespaceHandler?mappings?from?["?+?this.handlerMappingsLocation?+?"]");
          ?????}
          ?????try?{
          ??????Properties?mappings?=
          ????????PropertiesLoaderUtils.loadAllProperties(this.handlerMappingsLocation,?this.classLoader);
          ??????if?(logger.isDebugEnabled())?{
          ???????logger.debug("Loaded?NamespaceHandler?mappings:?"?+?mappings);
          ??????}
          ??????handlerMappings?=?new?ConcurrentHashMap<>(mappings.size());
          ??????CollectionUtils.mergePropertiesIntoMap(mappings,?handlerMappings);
          ??????this.handlerMappings?=?handlerMappings;
          ?????}
          ?????catch?(IOException?ex)?{
          ??????throw?new?IllegalStateException(
          ????????"Unable?to?load?NamespaceHandler?mappings?from?location?["?+?this.handlerMappingsLocation?+?"]",?ex);
          ?????}
          ????}
          ???}
          ??}
          ??return?handlerMappings;
          ?}

          我們看到它使用了雙重檢測鎖,并且還定義了一個局部變量handlerMappings,這是非常高明之處。

          使用局部變量相對于不使用局部變量,可以提高性能。主要是由于 volatile 變量創(chuàng)建對象時需要禁止指令重排序,需要一些額外的操作。

          7.3 LogFactory

          mybatis提供LogFactory類是為了創(chuàng)建日志對象,根據(jù)引入的jar包,決定使用哪種方式打印日志。具體代碼如下:

          public?final?class?LogFactory?{

          ??public?static?final?String?MARKER?=?"MYBATIS";

          ??private?static?Constructor?logConstructor;

          ??static?{
          ????tryImplementation(new?Runnable()?{
          ??????@Override
          ??????public?void?run()?{
          ????????useSlf4jLogging();
          ??????}
          ????});
          ????tryImplementation(new?Runnable()?{
          ??????@Override
          ??????public?void?run()?{
          ????????useCommonsLogging();
          ??????}
          ????});
          ????tryImplementation(new?Runnable()?{
          ??????@Override
          ??????public?void?run()?{
          ????????useLog4J2Logging();
          ??????}
          ????});
          ????tryImplementation(new?Runnable()?{
          ??????@Override
          ??????public?void?run()?{
          ????????useLog4JLogging();
          ??????}
          ????});
          ????tryImplementation(new?Runnable()?{
          ??????@Override
          ??????public?void?run()?{
          ????????useJdkLogging();
          ??????}
          ????});
          ????tryImplementation(new?Runnable()?{
          ??????@Override
          ??????public?void?run()?{
          ????????useNoLogging();
          ??????}
          ????});
          ??}

          ??private?LogFactory()?{
          ????//?disable?construction
          ??}

          ??public?static?Log?getLog(Class?aClass)?{
          ????return?getLog(aClass.getName());
          ??}

          ??public?static?Log?getLog(String?logger)?{
          ????try?{
          ??????return?logConstructor.newInstance(logger);
          ????}?catch?(Throwable?t)?{
          ??????throw?new?LogException("Error?creating?logger?for?logger?"?+?logger?+?".??Cause:?"?+?t,?t);
          ????}
          ??}

          ??public?static?synchronized?void?useCustomLogging(Class?clazz)?{
          ????setImplementation(clazz);
          ??}

          ??public?static?synchronized?void?useSlf4jLogging()?{
          ????setImplementation(org.apache.ibatis.logging.slf4j.Slf4jImpl.class);
          ??}

          ??public?static?synchronized?void?useCommonsLogging()?{
          ????setImplementation(org.apache.ibatis.logging.commons.JakartaCommonsLoggingImpl.class);
          ??}

          ??public?static?synchronized?void?useLog4JLogging()?{
          ????setImplementation(org.apache.ibatis.logging.log4j.Log4jImpl.class);
          ??}

          ??public?static?synchronized?void?useLog4J2Logging()?{
          ????setImplementation(org.apache.ibatis.logging.log4j2.Log4j2Impl.class);
          ??}

          ??public?static?synchronized?void?useJdkLogging()?{
          ????setImplementation(org.apache.ibatis.logging.jdk14.Jdk14LoggingImpl.class);
          ??}

          ??public?static?synchronized?void?useStdOutLogging()?{
          ????setImplementation(org.apache.ibatis.logging.stdout.StdOutImpl.class);
          ??}

          ??public?static?synchronized?void?useNoLogging()?{
          ????setImplementation(org.apache.ibatis.logging.nologging.NoLoggingImpl.class);
          ??}

          ??private?static?void?tryImplementation(Runnable?runnable)?{
          ????if?(logConstructor?==?null)?{
          ??????try?{
          ????????runnable.run();
          ??????}?catch?(Throwable?t)?{
          ????????//?ignore
          ??????}
          ????}
          ??}

          ??private?static?void?setImplementation(Class?implClass)?{
          ????try?{
          ??????Constructor?candidate?=?implClass.getConstructor(String.class);
          ??????Log?log?=?candidate.newInstance(LogFactory.class.getName());
          ??????if?(log.isDebugEnabled())?{
          ????????log.debug("Logging?initialized?using?'"?+?implClass?+?"'?adapter.");
          ??????}
          ??????logConstructor?=?candidate;
          ????}?catch?(Throwable?t)?{
          ??????throw?new?LogException("Error?setting?Log?implementation.??Cause:?"?+?t,?t);
          ????}
          ??}
          }

          這段代碼非常經(jīng)典,但它卻是一個不走尋常路的單例模式。因為它創(chuàng)建的實例對象,可能存在多種情況,根據(jù)引入不同的jar包,加載不同的類創(chuàng)建實例對象。如果有一個創(chuàng)建成功,則用它作為整個類的實例對象。

          這里有個非常巧妙的地方是:使用了很多tryImplementation方法,方便后面進(jìn)行擴(kuò)展。不然要寫很多,又臭又長的if...else判斷。

          此外,它跟常規(guī)的單例模式的區(qū)別是,LogFactory類中定義的實例對象是Log類型,并且getLog方法返回的參數(shù)類型也是Log,不是LogFactory。

          最關(guān)鍵的一點是:getLog方法中是通過構(gòu)造器的newInstance方法創(chuàng)建的實例對象,每次請求getLog方法都會返回一個新的實例,它其實是一個多例模式。

          7.4 ErrorContext

          mybatis提供ErrorContext類記錄了錯誤信息的上下文,方便后續(xù)處理。

          那么它是如何實現(xiàn)單例模式的呢?關(guān)鍵代碼如下:

          public?class?ErrorContext?{
          ??...
          ??private?static?final?ThreadLocal?LOCAL?=?new?ThreadLocal();
          ??
          ??private?ErrorContext()?{
          ??}
          ??
          ??public?static?ErrorContext?instance()?{
          ????ErrorContext?context?=?LOCAL.get();
          ????if?(context?==?null)?{
          ??????context?=?new?ErrorContext();
          ??????LOCAL.set(context);
          ????}
          ????return?context;
          ??}
          ??...
          }??

          我們可以看到,ErrorContext跟傳統(tǒng)的單例模式不一樣,它改良了一下。它使用了餓漢模式,并且使用ThreadLocal,保證每個線程中的實例對象是單例的。這樣看來,ErrorContext類創(chuàng)建的對象不是唯一的,它其實也是多例模式的一種。

          7.5 spring的單例

          以前在spring中要定義一個bean,需要在xml文件中做如下配置:

          "test"?class="com.susan.Test"?init-method="init"?scope="singleton">

          在bean標(biāo)簽上有個scope屬性,我們可以通過指定該屬性控制bean實例是單例的,還是多例的。如果值為singleton,代表是單例的。當(dāng)然如果該參數(shù)不指定,默認(rèn)也是單例的。如果值為prototype,則代表是多例的。

          在spring的AbstractBeanFactory類的doGetBean方法中,有這樣一段代碼:

          if?(mbd.isSingleton())?{
          ????sharedInstance?=?getSingleton(beanName,?()?->?{
          ??????return?createBean(beanName,?mbd,?args);
          ??});
          ??bean?=?getObjectForBeanInstance(sharedInstance,?name,?beanName,?mbd);
          }?else?if?(mbd.isPrototype())?{
          ????Object?prototypeInstance?=?createBean(beanName,?mbd,?args);
          ????bean?=?getObjectForBeanInstance(prototypeInstance,?name,?beanName,?mbd);
          }?else?{
          ????....
          }

          這段代碼我為了好演示,看起來更清晰,我特地簡化過的。它的主要邏輯如下:

          1. 判斷如果scope是singleton,則調(diào)用getSingleton方法獲取實例。
          2. 如果scope是prototype,則直接創(chuàng)建bean實例,每次會創(chuàng)建一個新實例。
          3. 如果scope是其他值,則允許我們自定bean的創(chuàng)建過程。

          其中g(shù)etSingleton方法主要代碼如下:

          public?Object?getSingleton(String?beanName,?ObjectFactory?singletonFactory)?{
          ??Assert.notNull(beanName,?"Bean?name?must?not?be?null");
          ??synchronized?(this.singletonObjects)?{
          ???Object?singletonObject?=?this.singletonObjects.get(beanName);
          ???if?(singletonObject?==?null)?{
          ??????????singletonObject?=?singletonFactory.getObject();
          ?????????if?(newSingleton)?{
          ???????????addSingleton(beanName,?singletonObject);
          ????????}
          ???}
          ???return?singletonObject;
          ??}
          }

          有個關(guān)鍵的singletonObjects對象,其實是一個ConcurrentHashMap集合:

          private?final?Map?singletonObjects?=?new?ConcurrentHashMap<>(256);

          getSingleton方法的主要邏輯如下:

          1. 根據(jù)beanName先從singletonObjects集合中獲取bean實例。
          2. 如果bean實例不為空,則直接返回該實例。
          3. 如果bean實例為空,則通過getObject方法創(chuàng)建bean實例,然后通過addSingleton方法,將該bean實例添加到singletonObjects集合中。
          4. 下次再通過beanName從singletonObjects集合中,就能獲取到bean實例了。

          在這里spring是通過ConcurrentHashMap集合來保證對象的唯一性。

          最后留給大家?guī)讉€小問題思考一下:

          1. 多例模式 和 多對象模式有什么區(qū)別?
          2. java框架中有些單例模式用的不規(guī)范,我要參考不?
          3. spring的單例,只是結(jié)果是單例的,但完全沒有遵循單例模式的固有寫法,它也算是單例模式嗎?

          歡迎大家給我留言,說出你心中的答案。

          碼字不易,如果讀了文章有些收獲的話,請幫我點贊一下,謝謝你的支持和鼓勵。

          瀏覽 25
          點贊
          評論
          收藏
          分享

          手機(jī)掃一掃分享

          分享
          舉報
          評論
          圖片
          表情
          推薦
          點贊
          評論
          收藏
          分享

          手機(jī)掃一掃分享

          分享
          舉報
          <kbd id="afajh"><form id="afajh"></form></kbd>
          <strong id="afajh"><dl id="afajh"></dl></strong>
            <del id="afajh"><form id="afajh"></form></del>
                1. <th id="afajh"><progress id="afajh"></progress></th>
                  <b id="afajh"><abbr id="afajh"></abbr></b>
                  <th id="afajh"><progress id="afajh"></progress></th>
                  日本护士XXXX1819 | 97中文字幕第二十二页 | 国产成人精品一区二区三区四区 | 依人大香蕉乱在线 | 豆花官网进入免费操逼 |