精品欧美一区二区三区在线观看 _久久久久国色av免费观看性色_国产精品久久在线观看_亚洲第一综合网站_91精品又粗又猛又爽_小泽玛利亚一区二区免费_91亚洲精品国偷拍自产在线观看 _久久精品视频在线播放_美女精品久久久_欧美日韩国产成人在线

深入探索單例模式,真不簡單

開發 前端
單例模式無論在我們面試,還是日常工作中,都會面對的問題。但很多單例模式的細節,值得我們深入探索一下。這篇文章透過單例模式,串聯了多方面基礎知識,非常值得一讀。

[[429703]]

前言

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

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

1 什么是單例模式?

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

該類負責創建自己的對象,同時確保只有一個對象被創建。一般常用在工具類的實現或創建對象需要消耗資源的業務場景。

單例模式的特點:

  • 類構造器私有
  • 持有自己類的引用
  • 對外提供獲取實例的靜態方法

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

  1. public class SimpleSingleton { 
  2.     //持有自己類的引用 
  3.     private static final SimpleSingleton INSTANCE = new SimpleSingleton(); 
  4.  
  5.     //私有的構造方法 
  6.     private SimpleSingleton() { 
  7.     } 
  8.     //對外提供獲取實例的靜態方法 
  9.     public static SimpleSingleton getInstance() { 
  10.         return INSTANCE; 
  11.     } 
  12.      
  13.     public static void main(String[] args) { 
  14.         System.out.println(SimpleSingleton.getInstance().hashCode()); 
  15.         System.out.println(SimpleSingleton.getInstance().hashCode()); 
  16.     } 

打印結果:

  1. 1639705018 
  2. 1639705018 

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

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

不信,我們一起往下看。

2 餓漢和懶漢模式

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

2.1 餓漢模式

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

  1. public class SimpleSingleton { 
  2.     //持有自己類的引用 
  3.     private static final SimpleSingleton INSTANCE = new SimpleSingleton(); 
  4.  
  5.     //私有的構造方法 
  6.     private SimpleSingleton() { 
  7.     } 
  8.     //對外提供獲取實例的靜態方法 
  9.     public static SimpleSingleton getInstance() { 
  10.         return INSTANCE; 
  11.     } 

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

  1. public class SimpleSingleton { 
  2.     //持有自己類的引用 
  3.     private static final SimpleSingleton INSTANCE; 
  4.     static { 
  5.        INSTANCE = new SimpleSingleton(); 
  6.     } 
  7.  
  8.     //私有的構造方法 
  9.     private SimpleSingleton() { 
  10.     } 
  11.     //對外提供獲取實例的靜態方法 
  12.     public static SimpleSingleton getInstance() { 
  13.         return INSTANCE; 
  14.     } 

使用靜態代碼塊的方式實例化INSTANCE對象。

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

  1. private static final SimpleSingleton INSTANCE = new SimpleSingleton(); 

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

還真是啊。

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

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

2.2 懶漢模式

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

  1. public class SimpleSingleton2 { 
  2.  
  3.     private static SimpleSingleton2 INSTANCE; 
  4.  
  5.     private SimpleSingleton2() { 
  6.     } 
  7.  
  8.     public static SimpleSingleton2 getInstance() { 
  9.         if (INSTANCE == null) { 
  10.             INSTANCE = new SimpleSingleton2(); 
  11.         } 
  12.         return INSTANCE; 
  13.     } 

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

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

2.3 synchronized關鍵字

上面的代碼有什么問題?

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

那么,要如何改進呢?

答:最簡單的辦法就是使用synchronized關鍵字。

改進后的代碼如下:

  1. public class SimpleSingleton3 { 
  2.     private static SimpleSingleton3 INSTANCE; 
  3.  
  4.     private SimpleSingleton3() { 
  5.     } 
  6.  
  7.     public synchronized static SimpleSingleton3 getInstance() { 
  8.         if (INSTANCE == null) { 
  9.             INSTANCE = new SimpleSingleton3(); 
  10.         } 
  11.         return INSTANCE; 
  12.     } 
  13.     public static void main(String[] args) { 
  14.         System.out.println(SimpleSingleton3.getInstance().hashCode()); 
  15.         System.out.println(SimpleSingleton3.getInstance().hashCode()); 
  16.     } 

在getInstance方法上加synchronized關鍵字,保證在并發的情況下,只有一個線程能創建INSTANCE對象的實例。

這樣總可以了吧?

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

有什么問題?

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

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

2.4 餓漢和懶漢模式的區別

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

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

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

3 雙重檢查鎖

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

那么,它是如何實現單例的呢?

3.1 如何實現單例?

具體代碼如下:

  1. public class SimpleSingleton4 { 
  2.  
  3.     private static SimpleSingleton4 INSTANCE; 
  4.  
  5.     private SimpleSingleton4() { 
  6.     } 
  7.  
  8.     public static SimpleSingleton4 getInstance() { 
  9.         if (INSTANCE == null) { 
  10.             synchronized (SimpleSingleton4.class) { 
  11.                 if (INSTANCE == null) { 
  12.                     INSTANCE = new SimpleSingleton4(); 
  13.                 } 
  14.             } 
  15.         } 
  16.         return INSTANCE; 
  17.     } 

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

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

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

比如:線程a和線程b同時調用getInstance方法,假如同時判斷INSTANCE都為空,這時會同時進行搶鎖。

假如線程a先搶到鎖,開始執行synchronized關鍵字包含的代碼,此時線程b處于等待狀態。

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

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

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

3.2 volatile關鍵字

上面的代碼還有啥問題?

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

getInstance方法的這段代碼,我是按1、2、3、4、5這種順序寫的,希望也按這個順序執行。

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

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

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

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

  1. public class SimpleSingleton7 { 
  2.  
  3.     private volatile static SimpleSingleton7 INSTANCE; 
  4.  
  5.     private SimpleSingleton7() { 
  6.     } 
  7.  
  8.     public static SimpleSingleton7 getInstance() { 
  9.         if (INSTANCE == null) { 
  10.             synchronized (SimpleSingleton7.class) { 
  11.                 if (INSTANCE == null) { 
  12.                     INSTANCE = new SimpleSingleton7(); 
  13.                 } 
  14.             } 
  15.         } 
  16.         return INSTANCE; 
  17.     } 

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

雙重檢查鎖的機制既保證了線程安全,又比直接上鎖提高了執行效率,還節省了內存空間。

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

4 靜態內部類

靜態內部類顧名思義是通過靜態的內部類來實現單例模式的。

那么,它是如何實現單例的呢?

4.1 如何實現單例模式?

具體代碼如下:

  1. public class SimpleSingleton5 { 
  2.  
  3.     private SimpleSingleton5() { 
  4.     } 
  5.  
  6.     public static SimpleSingleton5 getInstance() { 
  7.         return Inner.INSTANCE; 
  8.     } 
  9.  
  10.     private static class Inner { 
  11.         private static final SimpleSingleton5 INSTANCE = new SimpleSingleton5(); 
  12.     } 

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

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

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

4.2 反射漏洞

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

  1. Class<SimpleSingleton5> simpleSingleton5Class = SimpleSingleton5.class; 
  2. try { 
  3.     SimpleSingleton5 newInstance = simpleSingleton5Class.newInstance(); 
  4.     System.out.println(newInstance == SimpleSingleton5.getInstance()); 
  5. } catch (InstantiationException e) { 
  6.     e.printStackTrace(); 
  7. } catch (IllegalAccessException e) { 
  8.     e.printStackTrace(); 

上面代碼打印結果是false。

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

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

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

改造后的代碼如下:

  1. public class SimpleSingleton5 { 
  2.  
  3.     private SimpleSingleton5() { 
  4.         if(Inner.INSTANCE != null) { 
  5.            throw new RuntimeException("不能支持重復實例化"); 
  6.        } 
  7.     } 
  8.  
  9.     public static SimpleSingleton5 getInstance() { 
  10.         return Inner.INSTANCE; 
  11.     } 
  12.  
  13.     private static class Inner { 
  14.         private static final SimpleSingleton5 INSTANCE = new SimpleSingleton5(); 
  15.         } 
  16.     } 
  17.  

如果此時,你認為這種靜態內部類,實現單例模式的方法,已經完美了。

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

4.3 反序列化漏洞

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

我們可以把類的對象先保存到內存,或者某個文件當中。后面在某個時刻,再恢復成原始對象。

具體代碼如下:

  1. public class SimpleSingleton5 implements Serializable { 
  2.  
  3.     private SimpleSingleton5() { 
  4.         if (Inner.INSTANCE != null) { 
  5.             throw new RuntimeException("不能支持重復實例化"); 
  6.         } 
  7.     } 
  8.  
  9.     public static SimpleSingleton5 getInstance() { 
  10.         return Inner.INSTANCE; 
  11.     } 
  12.  
  13.     private static class Inner { 
  14.         private static final SimpleSingleton5 INSTANCE = new SimpleSingleton5(); 
  15.     } 
  16.  
  17.     private static void writeFile() { 
  18.         FileOutputStream fos = null
  19.         ObjectOutputStream oos = null
  20.         try { 
  21.             SimpleSingleton5 simpleSingleton5 = SimpleSingleton5.getInstance(); 
  22.             fos = new FileOutputStream(new File("test.txt")); 
  23.             oos = new ObjectOutputStream(fos); 
  24.             oos.writeObject(simpleSingleton5); 
  25.             System.out.println(simpleSingleton5.hashCode()); 
  26.         } catch (FileNotFoundException e) { 
  27.             e.printStackTrace(); 
  28.         } catch (IOException e) { 
  29.             e.printStackTrace(); 
  30.         } finally { 
  31.             if (oos != null) { 
  32.                 try { 
  33.                     oos.close(); 
  34.                 } catch (IOException e) { 
  35.                     e.printStackTrace(); 
  36.                 } 
  37.             } 
  38.             if (fos != null) { 
  39.                 try { 
  40.                     fos.close(); 
  41.                 } catch (IOException e) { 
  42.                     e.printStackTrace(); 
  43.                 } 
  44.             } 
  45.  
  46.         } 
  47.     } 
  48.  
  49.     private static void readFile() { 
  50.         FileInputStream fis = null
  51.         ObjectInputStream ois = null
  52.         try { 
  53.             fis = new FileInputStream(new File("test.txt")); 
  54.             ois = new ObjectInputStream(fis); 
  55.             SimpleSingleton5 myObject = (SimpleSingleton5) ois.readObject(); 
  56.  
  57.             System.out.println(myObject.hashCode()); 
  58.         } catch (FileNotFoundException e) { 
  59.             e.printStackTrace(); 
  60.         } catch (IOException e) { 
  61.             e.printStackTrace(); 
  62.         } catch (ClassNotFoundException e) { 
  63.             e.printStackTrace(); 
  64.         } finally { 
  65.             if (ois != null) { 
  66.                 try { 
  67.                     ois.close(); 
  68.                 } catch (IOException e) { 
  69.                     e.printStackTrace(); 
  70.                 } 
  71.             } 
  72.             if (fis != null) { 
  73.                 try { 
  74.                     fis.close(); 
  75.                 } catch (IOException e) { 
  76.                     e.printStackTrace(); 
  77.                 } 
  78.             } 
  79.         } 
  80.     } 
  81.  
  82.     public static void main(String[] args) { 
  83.         writeFile(); 
  84.         readFile(); 
  85.     } 

運行之后,發現序列化和反序列化后對象的hashCode不一樣:

  1. 189568618 
  2. 793589513 

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

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

答:重新readResolve方法。

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

  1. private Object readResolve() throws ObjectStreamException { 
  2.     return Inner.INSTANCE; 

運行結果如下:

  1. 290658609 
  2. 290658609 

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

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

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

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

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

還是費了不少勁。

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

納尼。。。

5 枚舉

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

簡單的用法:

  1. public enum  SimpleSingleton7 { 
  2.     INSTANCE; 
  3.      
  4.     public void doSamething() { 
  5.         System.out.println("doSamething"); 
  6.     } 
  7. }    

在調用的地方:

  1. public class SimpleSingleton7Test { 
  2.  
  3.     public static void main(String[] args) { 
  4.         SimpleSingleton7.INSTANCE.doSamething(); 
  5.     } 

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

當然,在枚舉對象唯一性的這個特性,還能創建其他的單例對象,例如:

  1. public enum  SimpleSingleton7 { 
  2.     INSTANCE; 
  3.      
  4.     private Student instance; 
  5.      
  6.     SimpleSingleton7() { 
  7.        instance = new Student(); 
  8.     } 
  9.      
  10.     public Student getInstance() { 
  11.        return instance; 
  12.     } 
  13.  
  14. class Student { 

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

在java大神Joshua Bloch的經典書籍《Effective Java》中說過:

單元素的枚舉類型已經成為實現Singleton的最佳方法。

6 多例模式

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

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

具體實現代碼如下:

  1. public class SimpleMultiPattern { 
  2.     //持有自己類的引用 
  3.     private static final SimpleMultiPattern INSTANCE1 = new SimpleMultiPattern(); 
  4.     private static final SimpleMultiPattern INSTANCE2 = new SimpleMultiPattern(); 
  5.  
  6.     //私有的構造方法 
  7.     private SimpleMultiPattern() { 
  8.     } 
  9.     //對外提供獲取實例的靜態方法 
  10.     public static SimpleMultiPattern getInstance(int type) { 
  11.         if(type == 1) { 
  12.           return INSTANCE1; 
  13.         } 
  14.         return INSTANCE2; 
  15.     } 

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

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

答:不,它是通過享元模式實現的。

那么,多例模式和享元模式有什么區別?

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

7 真實使用場景

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

7.1 Runtimejdk

提供了Runtime類,我們可以通過這個類獲取系統的運行狀態。

比如可以通過它獲取cpu核數:

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

Runtime類的關鍵代碼如下:

  1. public class Runtime { 
  2.     private static Runtime currentRuntime = new Runtime(); 
  3.      
  4.     public static Runtime getRuntime() { 
  5.         return currentRuntime; 
  6.     } 
  7.  
  8.     private Runtime() {} 
  9.     ... 

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

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

不過它的優點是實現起來非常簡單。

7.2 NamespaceHandlerResolver

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

它的關鍵代碼如下:

  1. @Nullable 
  2. private volatile Map<String, Object> handlerMappings; 
  3.  
  4. private Map<String, Object> getHandlerMappings() { 
  5.   Map<String, Object> handlerMappings = this.handlerMappings; 
  6.   if (handlerMappings == null) { 
  7.    synchronized (this) { 
  8.     handlerMappings = this.handlerMappings; 
  9.     if (handlerMappings == null) { 
  10.      if (logger.isDebugEnabled()) { 
  11.       logger.debug("Loading NamespaceHandler mappings from [" + this.handlerMappingsLocation + "]"); 
  12.      } 
  13.      try { 
  14.       Properties mappings = 
  15.         PropertiesLoaderUtils.loadAllProperties(this.handlerMappingsLocation, this.classLoader); 
  16.       if (logger.isDebugEnabled()) { 
  17.        logger.debug("Loaded NamespaceHandler mappings: " + mappings); 
  18.       } 
  19.       handlerMappings = new ConcurrentHashMap<>(mappings.size()); 
  20.       CollectionUtils.mergePropertiesIntoMap(mappings, handlerMappings); 
  21.       this.handlerMappings = handlerMappings; 
  22.      } 
  23.      catch (IOException ex) { 
  24.       throw new IllegalStateException( 
  25.         "Unable to load NamespaceHandler mappings from location [" + this.handlerMappingsLocation + "]", ex); 
  26.      } 
  27.     } 
  28.    } 
  29.   } 
  30.   return handlerMappings; 
  31.  } 

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

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

7.3 LogFactory

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

  1. public final class LogFactory { 
  2.  
  3.   public static final String MARKER = "MYBATIS"
  4.  
  5.   private static Constructor<? extends Log> logConstructor; 
  6.  
  7.   static { 
  8.     tryImplementation(new Runnable() { 
  9.       @Override 
  10.       public void run() { 
  11.         useSlf4jLogging(); 
  12.       } 
  13.     }); 
  14.     tryImplementation(new Runnable() { 
  15.       @Override 
  16.       public void run() { 
  17.         useCommonsLogging(); 
  18.       } 
  19.     }); 
  20.     tryImplementation(new Runnable() { 
  21.       @Override 
  22.       public void run() { 
  23.         useLog4J2Logging(); 
  24.       } 
  25.     }); 
  26.     tryImplementation(new Runnable() { 
  27.       @Override 
  28.       public void run() { 
  29.         useLog4JLogging(); 
  30.       } 
  31.     }); 
  32.     tryImplementation(new Runnable() { 
  33.       @Override 
  34.       public void run() { 
  35.         useJdkLogging(); 
  36.       } 
  37.     }); 
  38.     tryImplementation(new Runnable() { 
  39.       @Override 
  40.       public void run() { 
  41.         useNoLogging(); 
  42.       } 
  43.     }); 
  44.   } 
  45.  
  46.   private LogFactory() { 
  47.     // disable construction 
  48.   } 
  49.  
  50.   public static Log getLog(Class<?> aClass) { 
  51.     return getLog(aClass.getName()); 
  52.   } 
  53.  
  54.   public static Log getLog(String logger) { 
  55.     try { 
  56.       return logConstructor.newInstance(logger); 
  57.     } catch (Throwable t) { 
  58.       throw new LogException("Error creating logger for logger " + logger + ".  Cause: " + t, t); 
  59.     } 
  60.   } 
  61.  
  62.   public static synchronized void useCustomLogging(Class<? extends Log> clazz) { 
  63.     setImplementation(clazz); 
  64.   } 
  65.  
  66.   public static synchronized void useSlf4jLogging() { 
  67.     setImplementation(org.apache.ibatis.logging.slf4j.Slf4jImpl.class); 
  68.   } 
  69.  
  70.   public static synchronized void useCommonsLogging() { 
  71.     setImplementation(org.apache.ibatis.logging.commons.JakartaCommonsLoggingImpl.class); 
  72.   } 
  73.  
  74.   public static synchronized void useLog4JLogging() { 
  75.     setImplementation(org.apache.ibatis.logging.log4j.Log4jImpl.class); 
  76.   } 
  77.  
  78.   public static synchronized void useLog4J2Logging() { 
  79.     setImplementation(org.apache.ibatis.logging.log4j2.Log4j2Impl.class); 
  80.   } 
  81.  
  82.   public static synchronized void useJdkLogging() { 
  83.     setImplementation(org.apache.ibatis.logging.jdk14.Jdk14LoggingImpl.class); 
  84.   } 
  85.  
  86.   public static synchronized void useStdOutLogging() { 
  87.     setImplementation(org.apache.ibatis.logging.stdout.StdOutImpl.class); 
  88.   } 
  89.  
  90.   public static synchronized void useNoLogging() { 
  91.     setImplementation(org.apache.ibatis.logging.nologging.NoLoggingImpl.class); 
  92.   } 
  93.  
  94.   private static void tryImplementation(Runnable runnable) { 
  95.     if (logConstructor == null) { 
  96.       try { 
  97.         runnable.run(); 
  98.       } catch (Throwable t) { 
  99.         // ignore 
  100.       } 
  101.     } 
  102.   } 
  103.  
  104.   private static void setImplementation(Class<? extends Log> implClass) { 
  105.     try { 
  106.       Constructor<? extends Log> candidate = implClass.getConstructor(String.class); 
  107.       Log log = candidate.newInstance(LogFactory.class.getName()); 
  108.       if (log.isDebugEnabled()) { 
  109.         log.debug("Logging initialized using '" + implClass + "' adapter."); 
  110.       } 
  111.       logConstructor = candidate; 
  112.     } catch (Throwable t) { 
  113.       throw new LogException("Error setting Log implementation.  Cause: " + t, t); 
  114.     } 
  115.   } 

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

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

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

最關鍵的一點是:getLog方法中是通過構造器的newInstance方法創建的實例對象,每次請求getLog方法都會返回一個新的實例,它其實是一個多例模式。

7.4 ErrorContext

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

那么它是如何實現單例模式的呢?關鍵代碼如下:

  1. public class ErrorContext { 
  2.   ... 
  3.   private static final ThreadLocal<ErrorContext> LOCAL = new ThreadLocal<ErrorContext>(); 
  4.    
  5.   private ErrorContext() { 
  6.   } 
  7.    
  8.   public static ErrorContext instance() { 
  9.     ErrorContext context = LOCAL.get(); 
  10.     if (context == null) { 
  11.       context = new ErrorContext(); 
  12.       LOCAL.set(context); 
  13.     } 
  14.     return context; 
  15.   } 
  16.   ... 
  17. }   

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

7.5 spring的單例

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

  1. <bean id="test" class="com.susan.Test" init-method="init" scope="singleton"

在bean標簽上有個scope屬性,我們可以通過指定該屬性控制bean實例是單例的,還是多例的。如果值為singleton,代表是單例的。當然如果該參數不指定,默認也是單例的。如果值為prototype,則代表是多例的。

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

  1. if (mbd.isSingleton()) { 
  2.     sharedInstance = getSingleton(beanName, () -> { 
  3.       return createBean(beanName, mbd, args); 
  4.   }); 
  5.   bean = getObjectForBeanInstance(sharedInstance, name, beanName, mbd); 
  6. else if (mbd.isPrototype()) { 
  7.     Object prototypeInstance = createBean(beanName, mbd, args); 
  8.     bean = getObjectForBeanInstance(prototypeInstance, name, beanName, mbd); 
  9. else { 
  10.     .... 

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

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

其中getSingleton方法主要代碼如下:

  1. public Object getSingleton(String beanName, ObjectFactory<?> singletonFactory) { 
  2.   Assert.notNull(beanName, "Bean name must not be null"); 
  3.   synchronized (this.singletonObjects) { 
  4.    Object singletonObject = this.singletonObjects.get(beanName); 
  5.    if (singletonObject == null) { 
  6.           singletonObject = singletonFactory.getObject(); 
  7.          if (newSingleton) { 
  8.            addSingleton(beanName, singletonObject); 
  9.         } 
  10.    } 
  11.    return singletonObject; 
  12.   } 

有個關鍵的singletonObjects對象,其實是一個ConcurrentHashMap集合:

  1. private final Map<String, Object> singletonObjects = new ConcurrentHashMap<>(256); 

getSingleton方法的主要邏輯如下:

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

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

最后留給大家幾個小問題思考一下:

1. 多例模式 和 多對象模式有什么區別?

2. java框架中有些單例模式用的不規范,我要參考不?

3. spring的單例,只是結果是單例的,但完全沒有遵循單例模式的固有寫法,它也算是單例模式嗎?

 

責任編輯:姜華 來源: 蘇三說技術
相關推薦

2014-02-24 14:45:23

XPath開發工具

2023-05-17 07:36:00

淺拷貝深拷貝對象

2017-12-25 15:35:36

iMac Pro芯片存儲

2020-12-16 07:36:46

Redis字符串數據

2021-03-02 08:50:31

設計單例模式

2021-02-01 10:01:58

設計模式 Java單例模式

2022-09-29 08:39:37

架構

2013-11-26 16:20:26

Android設計模式

2016-03-28 10:23:11

Android設計單例

2019-02-21 10:06:49

2014-12-19 10:07:10

C

2021-09-07 10:44:35

異步單例模式

2011-03-16 10:13:31

java單例模式

2021-02-07 23:58:10

單例模式對象

2022-06-07 08:55:04

Golang單例模式語言

2022-02-06 22:30:36

前端設計模式

2012-06-26 09:40:14

部署開發管理

2024-03-06 13:19:19

工廠模式Python函數

2024-02-04 12:04:17

2022-08-15 08:49:06

Go版本單例模式
點贊
收藏

51CTO技術棧公眾號

国产精品自拍首页| 日韩欧美国产一区二区在线播放| 欧美日韩视频在线一区二区观看视频| 国产成人无码专区| 欧美电影免费播放| 精品国产99国产精品| 久久精品免费一区二区| 一区二区三区视频网站| 成人精品一区二区三区中文字幕| 国产99在线|中文| 欧美成人精品激情在线视频| 色天天色综合| 亚洲一区二区三区免费在线观看| 亚洲国产私拍精品国模在线观看| 亚洲免费av一区二区三区| 黄色小网站在线观看| 91亚洲精品久久久蜜桃网站| 国产人妖伪娘一区91| 日韩无码精品一区二区三区| 日韩成人影院| 日韩精品欧美激情| 性久久久久久久久久久久久久| 午夜影院一区| 夜夜嗨av一区二区三区中文字幕| 涩涩日韩在线| 五月激情婷婷网| 国产福利一区二区| 国产欧美日韩高清| 成人毛片18女人毛片| 欧美激情一区| 精品国产一区二区三区久久久| 亚洲国产欧美视频| 草莓视频一区二区三区| 5566中文字幕一区二区电影| www.99在线| 都市激情亚洲综合| 精品久久久久国产| 日韩精品在线观看av| 精品麻豆一区二区三区| 国产精品无人区| 欧美日韩在线播放一区二区| 午夜性色福利影院| 成人av在线资源| 国产精品免费一区二区三区| www.com在线观看| 国产一区二区精品久久| 国产日韩欧美视频| 中文字字幕在线中文乱码| 日韩精品欧美成人高清一区二区| 欧美野外猛男的大粗鳮| 日本高清www免费视频| 激情一区二区| 8050国产精品久久久久久| 久久久久亚洲av无码专区| 欧美不卡一区| 欧美国产日韩一区二区在线观看| 亚洲国产123| 亚洲高清影视| 欧美肥臀大乳一区二区免费视频| 亚洲成人生活片| 欧美日韩一卡| 久久久免费观看| 日本在线视频免费| 亚洲美女毛片| 欧美一区二区三区……| 国产情侣自拍av| 久久一区二区三区四区五区| …久久精品99久久香蕉国产| 久久久久久久久久久影院| 亚欧美中日韩视频| 国产精品wwww| 国产美女免费视频| 高清av一区二区| 久久偷看各类wc女厕嘘嘘偷窃 | 欧美午夜精品久久久久久孕妇| 午夜激情福利在线| 亚洲成人精品综合在线| 欧美不卡视频一区| 91精品人妻一区二区| 超碰成人久久| 欧美激情一区二区三区高清视频| 丁香六月婷婷综合| 日韩成人dvd| 91香蕉电影院| 日韩在线免费播放| 中文字幕一区二区在线播放| 成人av在线播放观看| 亚洲优女在线| 91.成人天堂一区| 中文字幕第3页| 欧美呦呦网站| 国内精品久久影院| 波多野结衣小视频| 国产精品一区二区91| 精品无码久久久久国产| 欧美成人三区| 欧美色播在线播放| aaa一级黄色片| 奇米影视777在线欧美电影观看| 中文字幕亚洲欧美日韩2019| 国产一级在线视频| 麻豆免费看一区二区三区| 国产精品国模大尺度私拍| 国产视频三级在线观看播放| 亚洲一区视频在线| a在线观看免费视频| 久久丝袜视频| 久久大大胆人体| 无码一区二区三区| 不卡免费追剧大全电视剧网站| 色一情一乱一伦一区二区三欧美 | 国产小视频免费在线网址| 亚洲精品国产无套在线观| 日本新janpanese乱熟| 超碰成人在线观看| 久久夜色精品亚洲噜噜国产mv| 人人草在线观看| aaa亚洲精品一二三区| 中日韩在线视频| 日本美女久久| 亚洲天堂第一页| 国产精选第一页| 国产在线不卡视频| 深夜福利成人| 成人香蕉视频| 亚洲精品福利资源站| 免费在线观看国产精品| 精品一区二区成人精品| 日韩精品不卡| 欧美va在线观看| 日韩精品黄色网| 国产大片中文字幕| 国产精品亚洲专一区二区三区 | 欧美14一18处毛片| 91麻豆精品国产91久久久久久 | 少妇精品一区二区三区| 激情成人亚洲| 91日韩一区二区三区| 午夜精品一区二区三区四区| 国产精品久久久久av电视剧| 亚洲黄页视频免费观看| 国产第一页在线播放| 国产99久久久国产精品潘金| 欧美黄色免费网址| 白嫩白嫩国产精品| 久久久久久com| 免费观看成年人视频| 亚洲精品成人天堂一二三| xxxx在线免费观看| 2023国产精品久久久精品双| 国产中文字幕日韩| 婷婷在线视频| 欧美人妖巨大在线| 三级在线观看免费大全| 久久成人av少妇免费| 亚洲综合av一区| 国产精品毛片无码| 欧美高清在线视频观看不卡| 精品国产乱码久久久久久蜜臀网站| 成人免费在线播放视频| 欧美专区第二页| 欧美福利电影在线观看| 国产精品自拍首页| 亚洲日本天堂| 国产一区二区三区在线| 一本大道伊人av久久综合| 亚洲欧美日韩国产中文在线| 亚洲丝袜在线观看| 精品福利电影| 好吊色欧美一区二区三区 | 麻豆一区二区三区视频| 日韩成人激情| 97欧洲一区二区精品免费| 欧美黑人猛交的在线视频| 精品粉嫩aⅴ一区二区三区四区| 中国一级特黄毛片| 欧美韩国日本综合| 99视频在线观看视频| 在线成人亚洲| 日韩色妇久久av| 国产精选久久| 91国自产精品中文字幕亚洲| 国产高清在线看| 欧美一级二级在线观看| 久热这里只有精品6| 黄网在线免费看| 日韩精品福利在线| 96日本xxxxxⅹxxx17| 一区二区三区欧美视频| 国产免费看av| 国产一区二区三区四区在线观看| 可以在线看的av网站| 欧美伦理在线视频| 99re视频在线| 国产一区二区主播在线| 久久97久久97精品免视看| 欧美日韩伦理片| 日韩午夜在线影院| 无码任你躁久久久久久久| 一区二区三区.www| www.av天天| 国产·精品毛片| 91人人澡人人爽人人精品| 欧美精品综合| 日本精品一区二区三区不卡无字幕| 日韩成人精品| 国产精品对白刺激| 岛国片av在线| 日韩日本欧美亚洲| 欧美色18zzzzxxxxx| 日韩欧美精品在线视频| 亚洲一区二区人妻| 日韩欧美主播在线| 久久久综合久久久| 日韩一区日韩二区| 国产美女免费网站| www.欧美日韩| 亚洲性图第一页| 国产一区二区网址| 冲田杏梨av在线| 西西人体一区二区| 久久久久久久久久网| 欧美影院一区| 一区二区三区四区国产| 波多野结衣在线播放一区| 欧美成人第一区| 色爱综合av| 国产日韩一区二区| 成人h动漫免费观看网站| 国产噜噜噜噜久久久久久久久| 丁香六月综合| 欧美中文在线免费| 女海盗2成人h版中文字幕| 欧美极品少妇xxxxⅹ裸体艺术| 毛片在线播放a| 日韩在线观看你懂的| 国产在线一二| 亚洲少妇中文在线| 精品无人乱码| 亚洲视频在线观看| 日韩精品视频久久| 亚洲高清极品| 欧美极品色图| 偷拍一区二区| 欧美日韩一区二| 国产欧美高清视频在线| 欧美激情论坛| 欧美男gay| 日产国产精品精品a∨| 欧美中文一区二区| 亚洲精品视频一二三| 日韩成人影院| 国产免费一区二区三区四在线播放| 清纯唯美亚洲综合一区| 亚洲视频电影| 成人三级视频| 欧美一级片在线播放| 亚洲日本天堂| 国产精品爽爽爽爽爽爽在线观看| 国产精品久久亚洲不卡| 国产欧美精品在线播放| 成人影院网站ww555久久精品| 96pao国产成视频永久免费| 麻豆久久一区| 国产乱人伦精品一区二区| 婷婷综合一区| 亚洲高清123| 亚洲成人国产| 欧美国产日韩激情| 免费一区视频| www.激情小说.com| 国产麻豆成人精品| 美女久久久久久久久| 2022国产精品视频| 五月婷婷六月香| 亚洲人吸女人奶水| 日本少妇激情视频| 欧美自拍偷拍午夜视频| av中文字幕观看| 日韩精品黄色网| 黄网站app在线观看| 国内精品视频在线| 播放一区二区| 成人欧美一区二区| 国产亚洲电影| 国产一二三四区在线观看| 一区二区三区四区五区精品视频 | 亚洲大片免费观看| 8x8x8国产精品| 日夜干在线视频| 久久亚洲精品毛片| 在线观看爽视频| 亚洲自拍在线观看| 久久av超碰| 99er在线视频| 久久国产精品99久久人人澡| 中文字幕一区二区人妻电影丶| 91高清在线观看视频| 久久91精品国产91久久跳| 婷婷综合六月| 国产精成人品localhost| 欧美色蜜桃97| www.国产在线视频| 久久精品国产免费看久久精品| 国产精品手机在线观看| 中文字幕一区二区日韩精品绯色| 国产成人在线播放视频| 91精品免费观看| 国产在线视频网站| 久久久爽爽爽美女图片| 亚洲综合资源| 日韩一区国产在线观看| 99精品免费| 极品白嫩少妇无套内谢| 亚洲欧洲av在线| 欧美超碰在线观看| 精品亚洲男同gayvideo网站| 羞羞视频在线观看不卡| 国产精品亚洲一区二区三区| 中文精品一区二区| 成人毛片一区二区| 丁香天五香天堂综合| 91麻豆精品成人一区二区| 在线免费av一区| 你懂的在线观看视频网站| 国内偷自视频区视频综合| 蜜桃精品视频| 国产对白在线播放| 久久av资源站| 国产日韩精品中文字无码| 日本韩国一区二区| 日韩porn| 欧美在线视频导航| 神马久久影院| 国产视频一视频二| av成人免费在线| 精品成人久久久| 精品国产三级电影在线观看| 中文字幕有码在线观看| 91色精品视频在线| 国产精品99在线观看| 最新天堂在线视频| 中文字幕人成不卡一区| 国产一区二区三区三州| 日韩视频免费大全中文字幕| 欧美日韩va| 异国色恋浪漫潭| 国产一区二区三区国产| 精品国产欧美日韩不卡在线观看| 欧美人动与zoxxxx乱| 老司机在线永久免费观看| 国产精品入口夜色视频大尺度 | 中文区中文字幕免费看| 一区二区三区日韩在线| 国产精品美女午夜爽爽| 一区二区三区在线视频看| 国产在线乱码一区二区三区| 精品亚洲乱码一区二区 | 日韩精品视频无播放器在线看| 51精品国产黑色丝袜高跟鞋| 青草在线视频| 中文字幕v亚洲ⅴv天堂| 欧美成人高清视频在线观看| 在线观看成人免费| 成人高清免费观看| 精品人妻无码一区二区性色| 亚洲免费视频一区二区| 97欧美成人| 日韩中文字幕亚洲精品欧美| 成人精品鲁一区一区二区| 黄色大片网站在线观看| 亚洲性视频网站| 日韩五码电影| 阿v天堂2018| 国产欧美一区二区三区在线看蜜臀 | 5858s免费视频成人| 四虎影视成人| 久久免费视频1| 日本午夜一区二区| 黄视频网站免费看| 亚洲精品www久久久久久广东| 欧美日韩精品免费观看视完整| 夜夜春亚洲嫩草影视日日摸夜夜添夜| 国产精品一区二区久激情瑜伽| 欧美啪啪小视频| 久久精品国产91精品亚洲| 精品深夜福利视频| 538任你躁在线精品免费| 一区二区三区美女| 国产在线黄色| 不卡的av一区| 日韩精品一区第一页| 农村妇女精品一区二区| 亚洲品质视频自拍网| 日日夜夜精品视频| 国产精品人人妻人人爽人人牛| 亚洲免费观看高清完整 | 精品国产美女在线| 久久丝袜视频| 在线观看免费视频污|