前言
單例模式無論在我們面試,還是日常作業中,都會面對的問題,但很多單例模式的細節,值得我們深入探索一下,
這篇文章透過單例模式,串聯了多方面基礎知識,非常值得一讀,
最近無意間獲得一份BAT大廠大佬寫的刷題筆記,一下子打通了我的任督二脈,越來越覺得演算法沒有想象中那么難了,
BAT大佬寫的刷題筆記,讓我offer拿到手軟

1 什么是單例模式?
單例模式是一種非常常用的軟體設計模式,它定義是單例物件的類只能允許一個實體存在,
該類負責創建自己的物件,同時確保只有一個物件被創建,一般常用在工具類的實作或創建物件需要消耗資源的業務場景,
單例模式的特點:
- 類構造器私有
- 持有自己類的參考
- 對外提供獲取實體的靜態方法
我們先用一個簡單示例了解一下單例模式的用法,
public class SimpleSingleton {
//持有自己類的參考
private static final SimpleSingleton INSTANCE = new SimpleSingleton();
//私有的構造方法
private SimpleSingleton() {
}
//對外提供獲取實體的靜態方法
public static SimpleSingleton getInstance() {
return INSTANCE;
}
public static void main(String[] args) {
System.out.println(SimpleSingleton.getInstance().hashCode());
System.out.println(SimpleSingleton.getInstance().hashCode());
}
}
列印結果:
1639705018
1639705018
我們看到兩次獲取SimpleSingleton實體的hashCode是一樣的,說明兩次呼叫獲取到的是同一個物件,
可能很多朋友平時作業當中都是這么用的,但我要說這段代碼是有問題的,你會相信嗎?
不信,我們一起往下看,
2 餓漢和懶漢模式
在介紹單例模式的時候,必須要先介紹它的兩種非常著名的實作方式:餓漢模式 和 懶漢模式,
2.1 餓漢模式
實體在初始化的時候就已經建好了,不管你有沒有用到,先建好了再說,具體代碼如下:
public class SimpleSingleton {
//持有自己類的參考
private static final SimpleSingleton INSTANCE = new SimpleSingleton();
//私有的構造方法
private SimpleSingleton() {
}
//對外提供獲取實體的靜態方法
public static SimpleSingleton getInstance() {
return INSTANCE;
}
}
餓漢模式,其實還有一個變種:
public class SimpleSingleton {
//持有自己類的參考
private static final SimpleSingleton INSTANCE;
static {
INSTANCE = new SimpleSingleton();
}
//私有的構造方法
private SimpleSingleton() {
}
//對外提供獲取實體的靜態方法
public static SimpleSingleton getInstance() {
return INSTANCE;
}
}
使用靜態代碼塊的方式實體化INSTANCE物件,
使用餓漢模式的好處是:沒有執行緒安全的問題,但帶來的壞處也很明顯,
private static final SimpleSingleton INSTANCE = new SimpleSingleton();
一開始就實體化物件了,如果實體化程序非常耗時,并且最后這個物件沒有被使用,不是白白造成資源浪費嗎?
還真是啊,
這個時候你也許會想到,不用提前實體化物件,在真正使用的時候再實體化不就可以了?
這就是我接下來要介紹的:懶漢模式,
2.2 懶漢模式
顧名思義就是實體在用到的時候才去創建,“比較懶”,用的時候才去檢查有沒有實體,如果有則回傳,沒有則新建,具體代碼如下:
public class SimpleSingleton2 {
private static SimpleSingleton2 INSTANCE;
private SimpleSingleton2() {
}
public static SimpleSingleton2 getInstance() {
if (INSTANCE == null) {
INSTANCE = new SimpleSingleton2();
}
return INSTANCE;
}
}
示例中的INSTANCE物件一開始是空的,在呼叫getInstance方法才會真正實體化,
嗯,不錯不錯,但這段代碼還是有問題,
2.3 synchronized關鍵字
上面的代碼有什么問題?
答:假如有多個執行緒中都呼叫了getInstance方法,那么都走到 if (INSTANCE == null) 判斷時,可能同時成立,因為INSTANCE初始化時默認值是null,這樣會導致多個執行緒中同時創建INSTANCE物件,即INSTANCE物件被創建了多次,違背了只創建一個INSTANCE物件的初衷,
那么,要如何改進呢?
答:最簡單的辦法就是使用synchronized關鍵字,
改進后的代碼如下:
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關鍵字,保證在并發的情況下,只有一個執行緒能創建INSTANCE物件的實體,
這樣總可以了吧?
答:不好意思,還是有問題,
有什么問題?
答:使用synchronized關鍵字會消耗getInstance方法的性能,我們應該判斷當INSTANCE為空時才加鎖,如果不為空不應該加鎖,需要直接回傳,
這就需要使用下面要說的雙重檢查鎖了,
2.4 餓漢和懶漢模式的區別
but,在介紹雙重檢查鎖之前,先插播一個朋友們可能比較關心的話題:餓漢模式 和 懶漢模式 各有什么優缺點?
餓漢模式:優點是沒有執行緒安全的問題,缺點是浪費記憶體空間,懶漢模式:優點是沒有記憶體空間浪費的問題,缺點是如果控制不好,實際上不是單例的,
好了,下面可以安心的看看雙重檢查鎖,是如何保證性能的,同時又保證單例的,
3 雙重檢查鎖
雙重檢查鎖顧名思義會檢查兩次:在加鎖之前檢查一次是否為空,加鎖之后再檢查一次是否為空,
那么,它是如何實作單例的呢?
3.1 如何實作單例?
具體代碼如下:
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是否為空呢?
答:是為了防止在多執行緒并發的情況下,只會實體化一個物件,
比如:執行緒a和執行緒b同時呼叫getInstance方法,假如同時判斷INSTANCE都為空,這時會同時進行搶鎖,
假如執行緒a先搶到鎖,開始執行synchronized關鍵字包含的代碼,此時執行緒b處于等待狀態,
執行緒a創建完新實體了,釋放鎖了,此時執行緒b拿到鎖,進入synchronized關鍵字包含的代碼,如果沒有再判斷一次INSTANCE是否為空,則可能會重復創建實體,
所以需要在synchronized前后兩次判斷,
不要以為這樣就完了,還有問題呢?
3.2 volatile關鍵字
上面的代碼還有啥問題?
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這種順序寫的,希望也按這個順序執行,
但是java虛擬機實際上會做一些優化,對一些代碼指令進行重排,重排之后的順序可能就變成了:1、3、2、4、5,這樣在多執行緒的情況下同樣會創建多次實體,重排之后的代碼可能如下:
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關鍵字,具體代碼如下:
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關鍵字可以保證多個執行緒的可見性,但是不能保證原子性,同時它也能禁止指令重排,
雙重檢查鎖的機制既保證了執行緒安全,又比直接上鎖提高了執行效率,還節省了記憶體空間,
除了上面的單例模式之外,還有沒有其他的單例模式?
4 靜態內部類
靜態內部類顧名思義是通過靜態的內部類來實作單例模式的,
那么,它是如何實作單例的呢?
4.1 如何實作單例模式?
具體代碼如下:
public class SimpleSingleton5 {
private SimpleSingleton5() {
}
public static SimpleSingleton5 getInstance() {
return Inner.INSTANCE;
}
private static class Inner {
private static final SimpleSingleton5 INSTANCE = new SimpleSingleton5();
}
}
我們看到在SimpleSingleton5類中定義了一個靜態的內部類Inner,在SimpleSingleton5類的getInstance方法中,回傳的是內部類Inner的實體INSTANCE物件,
只有在程式第一次呼叫getInstance方法時,虛擬機才加載Inner并實體化INSTANCE物件,
java內部機制保證了,只有一個執行緒可以獲得物件鎖,其他的執行緒必須等待,保證物件的唯一性,
4.2 反射漏洞
上面的代碼看似完美,但還是有漏洞,如果其他人使用反射,依然能夠通過類的無參構造方式創建物件,例如:
Class<SimpleSingleton5> simpleSingleton5Class = SimpleSingleton5.class;
try {
SimpleSingleton5 newInstance = simpleSingleton5Class.newInstance();
System.out.println(newInstance == SimpleSingleton5.getInstance());
} catch (InstantiationException e) {
e.printStackTrace();
} catch (IllegalAccessException e) {
e.printStackTrace();
}
上面代碼列印結果是false,
由此看出,通過反射創建的物件,跟通過getInstance方法獲取的物件,并非同一個物件,也就是說,這個漏洞會導致SimpleSingleton5非單例,
那么,要如何防止這個漏洞呢?
答:這就需要在無參構造方式中判斷,如果非空,則拋出例外了,
改造后的代碼如下:
public class SimpleSingleton5 {
private SimpleSingleton5() {
if(Inner.INSTANCE != null) {
throw new RuntimeException("不能支持重復實體化");
}
}
public static SimpleSingleton5 getInstance() {
return Inner.INSTANCE;
}
private static class Inner {
private static final SimpleSingleton5 INSTANCE = new SimpleSingleton5();
}
}
}
如果此時,你認為這種靜態內部類,實作單例模式的方法,已經完美了,
那么,我要告訴你的是,你錯了,還有漏洞,,,
4.3 反序列化漏洞
眾所周知,java中的類通過實作Serializable介面,可以實作序列化,
我們可以把類的物件先保存到記憶體,或者某個檔案當中,后面在某個時刻,再恢復成原始物件,
具體代碼如下:
public class SimpleSingleton5 implements Serializable {
private SimpleSingleton5() {
if (Inner.INSTANCE != null) {
throw new RuntimeException("不能支持重復實體化");
}
}
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();
}
}
運行之后,發現序列化和反序列化后物件的hashCode不一樣:
189568618
793589513
說明,反序列化時創建了一個新物件,打破了單例模式物件唯一性的要求,
那么,如何解決這個問題呢?
答:重新readResolve方法,
在上面的實體中,增加如下代碼:
private Object readResolve() throws ObjectStreamException {
return Inner.INSTANCE;
}
運行結果如下:
290658609
290658609
我們看到序列化和反序列化實體物件的hashCode相同了,
做法很簡單,只需要在readResolve方法中,每次都回傳唯一的Inner.INSTANCE物件即可,
程式在反序列化獲取物件時,會去尋找readResolve()方法,
- 如果該方法不存在,則直接回傳新物件,
- 如果該方法存在,則按該方法的內容回傳物件,
- 如果我們之前沒有實體化單例物件,則會回傳null,
好了,到這來終于把坑都踩完了,
還是費了不少勁,
不過,我偷偷告訴你一句,其實還有更簡單的方法,哈哈哈,
納尼,,,
5 列舉
其實在java中列舉就是天然的單例,每一個實體只有一個物件,這是java底層內部機制保證的,
簡單的用法:
public enum SimpleSingleton7 {
INSTANCE;
public void doSamething() {
System.out.println("doSamething");
}
}
在呼叫的地方:
public class SimpleSingleton7Test {
public static void main(String[] args) {
SimpleSingleton7.INSTANCE.doSamething();
}
}
在列舉中實體物件INSTANCE是唯一的,所以它是天然的單例模式,
當然,在列舉物件唯一性的這個特性,還能創建其他的單例物件,例如:
public enum SimpleSingleton7 {
INSTANCE;
private Student instance;
SimpleSingleton7() {
instance = new Student();
}
public Student getInstance() {
return instance;
}
}
class Student {
}
jvm保證了列舉是天然的單例,并且不存在執行緒安全問題,此外,還支持序列化,
在java大神Joshua Bloch的經典書籍《Effective Java》中說過:
單元素的列舉型別已經成為實作Singleton的最佳方法,
6 多例模式
我們之前聊過的單例模式,都只會產生一個實體,但它其實還有一個變種,也就是我們接下來要聊的:多例模式,
多例模式顧名思義,它允許創建多個實體,但它的初衷是為了控制實體的個數,其他的跟單例模式差不多,
具體實作代碼如下:
public class SimpleMultiPattern {
//持有自己類的參考
private static final SimpleMultiPattern INSTANCE1 = new SimpleMultiPattern();
private static final SimpleMultiPattern INSTANCE2 = new SimpleMultiPattern();
//私有的構造方法
private SimpleMultiPattern() {
}
//對外提供獲取實體的靜態方法
public static SimpleMultiPattern getInstance(int type) {
if(type == 1) {
return INSTANCE1;
}
return INSTANCE2;
}
}
為了看起來更直觀,我把一些額外的安全相關代碼去掉了,
有些朋友可能會說:既然多例模式也是為了控制實體數量,那我們常見的池技術,比如:資料庫連接池,是不是通過多例模式實作的?
答:不,它是通過享元模式實作的,
那么,多例模式和享元模式有什么區別?
- 多例模式:跟單例模式一樣,純粹是為了控制實體數量,使用這種模式的類,通常是作為程式某個模塊的入口,
- 享元模式:它的側重點是物件之間的銜接,它把動態的、會變化的狀態剝離出來,共享不變的東西,
7 真實使用場景
最后,跟大家一起聊聊,單例模式的一些使用場景,我們主要看看在java的框架中,是如何使用單例模式,給有需要的朋友一個參考,
7.1 Runtime
jdk提供了Runtime類,我們可以通過這個類獲取系統的運行狀態,
比如可以通過它獲取cpu核數:
int availableProcessors = Runtime.getRuntime().availableProcessors();
Runtime類的關鍵代碼如下:
public class Runtime {
private static Runtime currentRuntime = new Runtime();
public static Runtime getRuntime() {
return currentRuntime;
}
private Runtime() {}
...
}
從上面的代碼我們可以看出,這確實是一個單例模式,并且是餓漢模式,
但根據文章之前講過的一些理論知識,你會發現Runtime類的這種單例模式實作方式,顯然不太好,實體物件既沒用final關鍵字修飾,也沒考慮物件實體化的性能消耗問題,
不過它的優點是實作起來非常簡單,
最近無意間獲得一份BAT大廠大佬寫的刷題筆記,一下子打通了我的任督二脈,越來越覺得演算法沒有想象中那么難了,
BAT大佬寫的刷題筆記,讓我offer拿到手軟
7.2 NamespaceHandlerResolver
spring提供的DefaultNamespaceHandlerResolver是為需要初始化默認命名空間處理器,是為了方便后面做標簽決議用的,
它的關鍵代碼如下:
@Nullable
private volatile Map<String, Object> handlerMappings;
private Map<String, Object> getHandlerMappings() {
Map<String, Object> 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 變數創建物件時需要禁止指令重排序,需要一些額外的操作,
7.3 LogFactory
mybatis提供LogFactory類是為了創建日志物件,根據引入的jar包,決定使用哪種方式列印日志,具體代碼如下:
public final class LogFactory {
public static final String MARKER = "MYBATIS";
private static Constructor<? extends Log> 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<? extends Log> 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<? extends Log> implClass) {
try {
Constructor<? extends Log> 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);
}
}
}
這段代碼非常經典,但它卻是一個不走尋常路的單例模式,因為它創建的實體物件,可能存在多種情況,根據引入不同的jar包,加載不同的類創建實體物件,如果有一個創建成功,則用它作為整個類的實體物件,
這里有個非常巧妙的地方是:使用了很多tryImplementation方法,方便后面進行擴展,不然要寫很多,又臭又長的if…else判斷,
此外,它跟常規的單例模式的區別是,LogFactory類中定義的實體物件是Log型別,并且getLog方法回傳的引數型別也是Log,不是LogFactory,
最關鍵的一點是:getLog方法中是通過構造器的newInstance方法創建的實體物件,每次請求getLog方法都會回傳一個新的實體,它其實是一個多例模式,
7.4 ErrorContext
mybatis提供ErrorContext類記錄了錯誤資訊的背景關系,方便后續處理,
那么它是如何實作單例模式的呢?關鍵代碼如下:
public class ErrorContext {
...
private static final ThreadLocal<ErrorContext> LOCAL = new ThreadLocal<ErrorContext>();
private ErrorContext() {
}
public static ErrorContext instance() {
ErrorContext context = LOCAL.get();
if (context == null) {
context = new ErrorContext();
LOCAL.set(context);
}
return context;
}
...
}
我們可以看到,ErrorContext跟傳統的單例模式不一樣,它改良了一下,它使用了餓漢模式,并且使用ThreadLocal,保證每個執行緒中的實體物件是單例的,這樣看來,ErrorContext類創建的物件不是唯一的,它其實也是多例模式的一種,
7.5 spring的單例
以前在spring中要定義一個bean,需要在xml檔案中做如下配置:
<bean id="test" class="com.susan.Test" init-method="init" scope="singleton">
在bean標簽上有個scope屬性,我們可以通過指定該屬性控制bean實體是單例的,還是多例的,如果值為singleton,代表是單例的,當然如果該引數不指定,默認也是單例的,如果值為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 {
....
}
這段代碼我為了好演示,看起來更清晰,我特地簡化過的,它的主要邏輯如下:
- 判斷如果scope是singleton,則呼叫getSingleton方法獲取實體,
- 如果scope是prototype,則直接創建bean實體,每次會創建一個新實體,
- 如果scope是其他值,則允許我們自定bean的創建程序,
其中getSingleton方法主要代碼如下:
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;
}
}
有個關鍵的singletonObjects物件,其實是一個ConcurrentHashMap集合:
private final Map<String, Object> singletonObjects = new ConcurrentHashMap<>(256);
getSingleton方法的主要邏輯如下:
- 根據beanName先從singletonObjects集合中獲取bean實體,
- 如果bean實體不為空,則直接回傳該實體,
- 如果bean實體為空,則通過getObject方法創建bean實體,然后通過addSingleton方法,將該bean實體添加到singletonObjects集合中,
- 下次再通過beanName從singletonObjects集合中,就能獲取到bean實體了,
在這里spring是通過ConcurrentHashMap集合來保證物件的唯一性,
最后留給大家幾個小問題思考一下:
- 多例模式 和 多物件模式有什么區別?
- java框架中有些單例模式用的不規范,我要參考不?
- spring的單例,只是結果是單例的,但完全沒有遵循單例模式的固有寫法,它也算是單例模式嗎?
歡迎大家給我留言,說出你心中的答案,
最近無意間獲得一份BAT大廠大佬寫的刷題筆記,一下子打通了我的任督二脈,越來越覺得演算法沒有想象中那么難了,
BAT大佬寫的刷題筆記,讓我offer拿到手軟
碼字不易,如果讀了文章有些識訓的話,請幫我點贊一下,謝謝你的支持和鼓勵,
轉載請註明出處,本文鏈接:https://www.uj5u.com/houduan/327981.html
標籤:java
上一篇:cgb2108-day15
