1.單例設計模式是什麼
單例設計模式就是一個類,只生成一個對象 ,給予其他類調用。
所以至少至少要保證三點:
-
構造器私有化。
-
類本身創建對象。
-
提供獲取對象的方法
2.常見的幾種單例創建方式
2.1 餓漢式
在類加載到內存,就實例化一個實例。
jvm保證線程安全簡單實用,簡單以用,線程安全。
缺點:不管是否用到,類加載時完成實例化了。有人說這個會影響程序啓動時間,影響很小。
public class Singleton01 {
// static 變量,在類加載時,初始化變量
private static final Singleton01 instance = new Singleton01();
//私有構造器,讓別人不能new
private Singleton01(){}
public static Singleton01 getInstance(){
return instance;
}
public static void main(String[] args) {
System.out.println(Singleton01.getInstance()==Singleton01.getInstance());
}
}
2.2 懶漢式 lazy loading(雙重檢查)
實現按需加載,同時帶來了線程不安全的問題,。
解決線程安全(同時保證效率):雙重檢查(volatile synchronized),減小加鎖範圍synchronized,提高效率,同時線程安全。
缺點:代碼變複雜了。
public class Singleton06 {
//雙重檢查volatile防止指令重排
private static volatile Singleton06 instance;
//私有構造器,讓別人不能new
private Singleton06(){}
//雙重檢查實現線程安全、效率較高的單例模式
public static Singleton06 getInstance(){
if(instance == null){
synchronized(Singleton06.class){
if(instance == null) {
instance = new Singleton06();
}
}
}
return instance;
}
public static void main(String[] args) {
System.out.println(Singleton06.getInstance()== Singleton06.getInstance());
}
}
2.3 靜態內部類
jvm保證單例, 加載外部類時不會加載內部類,這樣就實現了懶加載,同時也保證了線程安全。
public class Singleton07 {
//私有構造器,讓別人不能new
private Singleton07(){}
//靜態內部類,初始化 new Singleton07();
//jvm只加載一次類,同時初始化靜態變量
private static class Singleton07Holder{
private final static Singleton07 instance = new Singleton07();
}
public static Singleton07 getInstance(){
return Singleton07Holder.instance;
}
public static void main(String[] args) {
System.out.println(Singleton07.getInstance()== Singleton07.getInstance());
}
}
2.4 枚舉實現
據說這種是最完美的一種。枚舉解決線程同步,以及反序列化問題。
public enum Singleton08 {
INSTANCE;
public static void main(String[] args) {
System.out.println(Singleton08.INSTANCE== Singleton08.INSTANCE);
}
}
個人覺得:這四種都可以使用,推薦靜態內部類實現,但是沒有最好的,按需即可。
3.應用場景
只需要一個實例,秉承着多new一個實例,多浪費內存空間。
比如各種Manager、各種Factory。
我們開發中使用最多的是spring中ioc容器中的單實例bean的實例化。
在實例化對象之前,會鎖住singletonObjects map對象,具體如下。
synchronized (this.singletonObjects) {}
public Object getSingleton(String beanName, ObjectFactory<?> singletonFactory) {
Assert.notNull(beanName, "Bean name must not be null");
//鎖定singletonObjects對象
synchronized (this.singletonObjects) {
//從緩存中獲取單例對象
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 + "'");
}
//單例創建前的檢查,並將其放入singletonsCurrentlyInCreation中
beforeSingletonCreation(beanName);
boolean newSingleton = false;
boolean recordSuppressedExceptions = (this.suppressedExceptions == null);
if (recordSuppressedExceptions) {
this.suppressedExceptions = new LinkedHashSet<>();
}
try {
/**
* 核心: 從{@link org.springframework.beans.factory.ObjectFactory#getObject()}加載bean
*/
singletonObject = singletonFactory.getObject();
newSingleton = true;
}
catch (IllegalStateException ex) {
// Has the singleton object implicitly appeared in the meantime ->
// if yes, proceed with it since the exception indicates that state.
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;
}
//單例創建後的檢查,並將其從singletonsCurrentlyInCreation中移除
afterSingletonCreation(beanName);
}
if (newSingleton) {
addSingleton(beanName, singletonObject);
}
}
return singletonObject;
}
}
小小總結: 在開發過程中,只要不涉及自己重新搭建架構,基本上已經很少需要自己實例化單例了。spring已經幫我們完成了單實例的創建了。
面試必問設計模式,儘量從spring源碼中重新整理一遍23種設計模式。有興趣的同學可以看看我的源碼分析系列,目前正在分析spring源碼1、spring源碼解析之概況流程。
未完待續。