定義:確保一個類只有一個實例录粱,而且自行實例化并向整個系統(tǒng)提供這個實例。
單例模式的設計要點:
- 構造方法私有化靠益。
- 有指向自己實例的靜態(tài)私有引用疮鲫。
- 有對外提供自身實例的靜態(tài)公有方法。
根據(jù)實例化對象時機的不同分為三種:
一種是餓漢式單例昏苏,一種是懶漢式單例,還有一種是枚舉實現(xiàn)威沫,它是餓漢式單例的一種特殊情況贤惯。
-
餓漢式單例,在單例類被加載時候棒掠,就實例化一個對象交給自己的引用孵构。
/** * 餓漢式單例(可以使用) * * @author suvue * @date 2020/1/9 */ public class HungryStyle { private static HungryStyle instance = new HungryStyle(); private HungryStyle() { } public static HungryStyle getInstance() { return instance; } }
-
懶漢式單例,是指在調用取得實例方法的時候才會實例化對象烟很。其實懶漢模式的單例創(chuàng)建有很多種颈墅,這里列舉推薦使用的中l(wèi)兩種方式蜡镶。
雙重檢索方式
結合了其余方式做了改進,其他方式的缺點如將同步鎖加到getInstance()方法上恤筛,會導致速率很慢官还;而不進行雙重檢索(也就是不進行第二次校驗),就會有線程安全問題毒坛,假如一個線程進入了if (singleton == null)判斷語句塊望伦,還未來得及往下執(zhí)行,另一個線程也通過了這個判斷語句煎殷,這時便會產(chǎn)生多個實例屯伞。
/** * 懶漢式單例(雙重檢索,推薦使用) * * @author suvue * @date 2020/1/9 */ public class DoubleCheckStyle { //volatile的使用是為了防止指令重排豪直。 private static volatile DoubleCheckStyle instance = null; private DoubleCheckStyle() { } public static DoubleCheckStyle getInstance() { if (instance == null) { synchronized (DoubleCheckStyle.class) { if (instance == null) { //new對象的過程可拆解為三個過程: //1.為新對象分配內存空間 //2.將變量引用的指針指向內存地址劣摇。 //3.實例化對象的一系列過程 //假如一個線程執(zhí)行了1、2弓乙,還沒來得及執(zhí)行3饵撑,這時為它分配的執(zhí)行時間 //用完了,另一個線程進來唆貌,此時因為上一個線程執(zhí)行了2滑潘,那么它會以為已經(jīng) //實例化好對象了,就開心的拿著這個單例對象執(zhí)行操作去了锨咙,實際上只分配了 //內存空間和引用语卤,而沒有進行實例化,所以這個線程用的時候就會拋異常酪刀。 instance = new DoubleCheckStyle(); } } } return instance; } }
靜態(tài)內部類方式
這種方式和餓漢式單例一樣粹舵,都是采用類加載機制,但是不同的是骂倘,餓漢式在類加載時進行初始化眼滤,靜態(tài)內部類方式在調用getInstance方法時才會實例化。
優(yōu)點:兼顧了懶漢模式的內存優(yōu)化(使用時才初始化)以及餓漢模式的安全性(類的靜態(tài)屬性只會在第一次加載類的時候初始化历涝,所以JVM幫助我們保證了線程的安全性)诅需。
缺點:需要兩個類去完成這一實現(xiàn),雖然不會創(chuàng)建靜態(tài)內部類的對象荧库,但是其 Class 對象還是會被創(chuàng)建堰塌,而且是屬于永久帶的對象。因此創(chuàng)建好的單例分衫,一旦在后期被銷毀场刑,不能重新創(chuàng)建。
/** * 懶漢式單例(通過靜態(tài)內部類的方式實現(xiàn)蚪战,推薦使用) * * @author suvue * @date 2020/1/9 */ public class StaticInnerStyle { private StaticInnerStyle() { } private static class InnerInstance { private final static StaticInnerStyle INSTANCE = new StaticInnerStyle(); } public static StaticInnerStyle getInstance() { return InnerInstance.INSTANCE; } }
-
枚舉式單例借助JDK1.5中添加的枚舉來實現(xiàn)單例模式牵现。不僅能避免多線程同步問題铐懊,而且還能防止反序列化重新創(chuàng)建新的對象。
package cn.suvue.discipline.practice.designpattern.singleton; /** * 枚舉方式實現(xiàn)的單例 * * @author suvue * @date 2020/1/9 */ public class EnumStyle { private EnumStyle() { } private enum Singleton { /** * 單例 */ INSTANCE; private final EnumStyle instance; Singleton() { this.instance = new EnumStyle(); } private EnumStyle getInstance() { return instance; } } public static EnumStyle getInstance() { return Singleton.INSTANCE.getInstance(); } }
單例模式的優(yōu)點
- 在內存中只有一個對象瞎疼,節(jié)省內存空間科乎。
- 避免頻繁的創(chuàng)建銷毀對象,可以提高性能丑慎。
使用注意事項
只能使用單例類提供的方法得到單例對象喜喂,不要使用反射,否則將會實例化一個新對象竿裂。我們的代碼在反射面前就是裸奔的玉吁,它是一種非常規(guī)操作。
構造方法時私有的腻异,因此單例類不可被繼承进副。
多線程使用單例使用共享資源時,注意線程安全問題悔常。
-
防止反射對單例造成破壞的方法影斑,因為反射是基于構造方法拿到的實例,所以我們可以這么改一下:
private StaticInnerStyle() { if (getInstance()!=null){ throw new RuntimeException("調用失敗"); } }
單例模式在spring中應用
spring中使用的單例模式的懶加載机打,但是使用的是單例注冊表實現(xiàn)的矫户,先來看一個小例子。
package cn.suvue.discipline.practice.designpattern.singleton;
import java.util.concurrent.ConcurrentHashMap;
/**
* 注冊表實現(xiàn)的單例
*
* @author suvue
* @date 2020/1/9
*/
public class RegisterStyle {
private static ConcurrentHashMap<String, Object> register = new ConcurrentHashMap<String, Object>(32);
static {
RegisterStyle res = new RegisterStyle();
register.put(res.getClass().getName(), res);
}
private RegisterStyle() {
}
public static RegisterStyle getInstance(String name) {
if (name == null) {
name = "cn.suvue.discipline.practice.designpattern.singleton.RegisterStyle";
}
if (register.get(name) == null) {
try {
register.put(name, Class.forName(name).newInstance());
} catch (Exception e) {
e.printStackTrace();
}
}
return (RegisterStyle) register.get(name);
}
}
上述的代碼很簡單残邀,實現(xiàn)思路大同小異皆辽,唯一的不同是用到了ConcurrentHashMap。下面我們來看一下Spring中的應用芥挣。最經(jīng)典的就是BeanFactory的獲取bean的時候驱闷。
@SuppressWarnings("unchecked")
protected <T> T doGetBean(final String name, @Nullable final Class<T> requiredType,
@Nullable final Object[] args, boolean typeCheckOnly) throws BeansException {
//校驗bean名是否有非法字符
final String beanName = transformedBeanName(name);
Object bean;
Object sharedInstance = getSingleton(beanName);
if (sharedInstance != null && args == null) {
//...
//獲取bean的實例
bean = getObjectForBeanInstance(sharedInstance, name, beanName, null);
}
else {
//...
try {
//獲取并檢查bean的定義
final RootBeanDefinition mbd = getMergedLocalBeanDefinition(beanName);
checkMergedBeanDefinition(mbd, beanName, args);
//...
// 創(chuàng)建bean的實例 類定義是單例的情況.
if (mbd.isSingleton()) {
sharedInstance = getSingleton(beanName, () -> {
try {
return createBean(beanName, mbd, args);
}
catch (BeansException ex) {
//出錯了要銷毀bean
destroySingleton(beanName);
throw ex;
}
});
//獲取bean的實例
bean = getObjectForBeanInstance(sharedInstance, name, beanName, mbd);
}
//多例情況 這里不多分析
else if (mbd.isPrototype()) {
//...
}
//作用域相關代碼,這里不看它
//...
}
catch (BeansException ex) {
cleanupAfterBeanCreationFailure(beanName);
throw ex;
}
}
// Check if required type matches the type of the actual bean instance.
if (requiredType != null && !requiredType.isInstance(bean)) {
//校驗bean的類型是否與實際的相匹配
//...
}
return (T) bean;
}
上面是我簡化過的代碼空免,我們著重看下是單例情況空另,也就是getSingleton方法的具體實現(xiàn)。
/** 單例對象的緩存容器蹋砚,key是bean的名稱扼菠,value是bean的實例 */
private final Map<String, Object> singletonObjects = new ConcurrentHashMap<>(256);
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) {
//...
boolean newSingleton = false;
//...
try {
//這里實際上創(chuàng)建一個新的對象
//因為這里的singletonFactory
//實際上傳進來的是createBean(beanName, mbd, args)
singletonObject = singletonFactory.getObject();
newSingleton = true;
}
catch (IllegalStateException ex) {
//創(chuàng)建實例因為容器中已經(jīng)存在了,就拋出異常都弹,然后到容器中直接取單例對象
singletonObject = this.singletonObjects.get(beanName);
if (singletonObject == null) {
throw ex;
}
}
//...
if (newSingleton) {
//如果是新的實例對象娇豫,那么就添加到容器中
addSingleton(beanName, singletonObject);
}
}
return singletonObject;
}
}
下面我們看看spring是怎么往容器中放單例對象的。
protected void addSingleton(String beanName, Object singletonObject) {
synchronized (this.singletonObjects) {
//直接在同步代碼塊 執(zhí)行put操作
//上段代碼的getSingleton方法 用了一個synchronized
//本段代碼中addSingleton方法 也用了一個synchronized
//并且鎖的對象都是singletonObjects
this.singletonObjects.put(beanName, singletonObject);
//下面的代碼可以不用看畅厢,重要的是上面這行
this.singletonFactories.remove(beanName);
this.earlySingletonObjects.remove(beanName);
this.registeredSingletons.add(beanName);
}
}
以上是我的一些粗鄙觀點,希望看到本博文的大神能糾正其中的錯誤氮昧!萬分感謝哦框杜,以后我對單例有了新的認知了浦楣,會不斷來補充更新的,也希望大神們多提提意見咪辱!