單例模式是一種常用的軟件設計模式。在它的核心結構中只包含一個被稱為單例的特殊類。通過單例模式可以保證系統中一個類只有一個實例
注:Spring源碼的版本4.3.4
Spring依賴注入Bean實例默認是單例的,我們由此展開。
Spring的依賴注入(包括lazy-init方式)都是發生在AbstractBeanFactory的getBean里。getBean的doGetBean方法調用getSingleton進行bean的創建。lazy-init方式,在容器初始化時候進行調用,非lazy-init方式,在用戶向容器第一次索要bean時進行調用
同步線程安全的單例核心代碼:
/** * Return the (raw) singleton object registered under the given name. * <p>Checks already instantiated singletons and also allows for an early * reference to a currently created singleton (resolving a circular reference). * @param beanName the name of the bean to look for * @param allowEarlyReference whether early references should be created or not * @return the registered singleton object, or {@code null} if none found */ protected Object getSingleton(String beanName, boolean allowEarlyReference) { Object singletonObject = this.singletonObjects.get(beanName); if (singletonObject == null && isSingletonCurrentlyInCreation(beanName)) { synchronized (this.singletonObjects) { singletonObject = this.earlySingletonObjects.get(beanName); if (singletonObject == null && allowEarlyReference) { ObjectFactory<?> singletonFactory = this.singletonFactories.get(beanName); if (singletonFactory != null) { singletonObject = singletonFactory.getObject(); this.earlySingletonObjects.put(beanName, singletonObject); this.singletonFactories.remove(beanName); } } } } return (singletonObject != NULL_OBJECT ? singletonObject : null); }
從上面代碼可以看到,spring依賴注入時,使用了雙重判斷加鎖的單例模式,首先從緩存中獲取bean實例,如果為null,對緩存map加鎖,然后再從緩存中獲取bean,如果繼續為null,就創建一個bean。這樣雙重判斷,能夠避免在加鎖的瞬間,有其他依賴注入引發bean實例的創建,從而造成重復創建的結果。
在這里Spring並沒有使用私有構造方法來創建bean,而是通過singletonFactory.getObject()返回具體beanName對應的ObjectFactory來創建bean。我們一路跟蹤下去,發現實際上是調用了AbstractAutowireCapableBeanFactory的doCreateBean方法,返回了BeanWrapper包裝並創建的bean實例。
(ObjectFactory主要檢查是否有用戶定義的BeanPostProcessor后處理內容,並在創建bean時進行處理,如果沒有,就直接返回bean本身)
見如下代碼:
512行創建bean實例返回給BeanWrapper
540行addSingletonFactory增加beanName和ObjectFactory的鍵值對應關系。
/** * Actually create the specified bean. Pre-creation processing has already happened * at this point, e.g. checking {@code postProcessBeforeInstantiation} callbacks. * <p>Differentiates between default bean instantiation, use of a * factory method, and autowiring a constructor. * @param beanName the name of the bean * @param mbd the merged bean definition for the bean * @param args explicit arguments to use for constructor or factory method invocation * @return a new instance of the bean * @throws BeanCreationException if the bean could not be created * @see #instantiateBean * @see #instantiateUsingFactoryMethod * @see #autowireConstructor */ protected Object doCreateBean(final String beanName, final RootBeanDefinition mbd, final Object[] args) throws BeanCreationException { // Instantiate the bean. BeanWrapper instanceWrapper = null; if (mbd.isSingleton()) { instanceWrapper = this.factoryBeanInstanceCache.remove(beanName); } if (instanceWrapper == null) { instanceWrapper = createBeanInstance(beanName, mbd, args); } final Object bean = (instanceWrapper != null ? instanceWrapper.getWrappedInstance() : null); Class<?> beanType = (instanceWrapper != null ? instanceWrapper.getWrappedClass() : null); // Allow post-processors to modify the merged bean definition. synchronized (mbd.postProcessingLock) { if (!mbd.postProcessed) { try { applyMergedBeanDefinitionPostProcessors(mbd, beanType, beanName); } catch (Throwable ex) { throw new BeanCreationException(mbd.getResourceDescription(), beanName, "Post-processing of merged bean definition failed", ex); } mbd.postProcessed = true; } } // Eagerly cache singletons to be able to resolve circular references // even when triggered by lifecycle interfaces like BeanFactoryAware. boolean earlySingletonExposure = (mbd.isSingleton() && this.allowCircularReferences && isSingletonCurrentlyInCreation(beanName)); if (earlySingletonExposure) { if (logger.isDebugEnabled()) { logger.debug("Eagerly caching bean '" + beanName + "' to allow for resolving potential circular references"); } addSingletonFactory(beanName, new ObjectFactory<Object>() { @Override public Object getObject() throws BeansException { return getEarlyBeanReference(beanName, mbd, bean); } }); }
getEarlyBeanReference獲取bean的所有后處理器,並進行處理。如果是SmartInstantiationAwareBeanPostProcessor類型,就進行處理,如果沒有相關處理內容,就返回默認的實現。
/** * Obtain a reference for early access to the specified bean, * typically for the purpose of resolving a circular reference. * @param beanName the name of the bean (for error handling purposes) * @param mbd the merged bean definition for the bean * @param bean the raw bean instance * @return the object to expose as bean reference */ protected Object getEarlyBeanReference(String beanName, RootBeanDefinition mbd, Object bean) { Object exposedObject = bean; if (bean != null && !mbd.isSynthetic() && hasInstantiationAwareBeanPostProcessors()) { for (BeanPostProcessor bp : getBeanPostProcessors()) { if (bp instanceof SmartInstantiationAwareBeanPostProcessor) { SmartInstantiationAwareBeanPostProcessor ibp = (SmartInstantiationAwareBeanPostProcessor) bp; exposedObject = ibp.getEarlyBeanReference(exposedObject, beanName); if (exposedObject == null) { return null; } } } } return exposedObject; }
彩蛋在此:
各種單例實現方式(5種):懶漢模式,餓漢線程非安全模式,餓漢線程安全模式,內部類模式,枚舉模式。現在最推薦的方式是枚舉單例模式。對這些模式的描述和介紹,請仔細看代碼中的注釋,會有意想不到的收獲呦!
package com.xhengxuyuanzhi; /** * @author 微信公眾號:程序員之路 * 博客:http://www.cnblogs.com/chengxuyuanzhilu/ * * 餓漢式單例模式 * 特點:可以通過反射機制攻擊;線程安全[多個類加載器除外]。 */ public class HungryType { public static final HungryType instance = new HungryType(); private HungryType(){ //初始化HungryType要做的事 } public void splitAlipay() { System.out.println("餓漢式單利模式"); } public static void main(String[] args) { HungryType ht = HungryType.instance; ht.splitAlipay(); } }
package com.xhengxuyuanzhi; /** * @author 微信公眾號:程序員之路 * 博客:http://www.cnblogs.com/chengxuyuanzhilu/ * * 懶漢模式單例 * 特點:延時加載;線程不安全,多線程下不能正常工作; */ public class SluggardType { private static SluggardType instance = null; private SluggardType() { } public static SluggardType getInstance(){ if(instance == null){ instance = new SluggardType(); } return instance; } public void say(){ System.out.println("懶漢模式單例"); } public static void main(String[] args) { SluggardType.getInstance().say(); } }
package com.xhengxuyuanzhi; /** * @author 微信公眾號:程序員之路 * 博客:http://www.cnblogs.com/chengxuyuanzhilu/ * * 懶漢模式(雙重校驗鎖[不推薦])單例 */ public class SluggardType2 { //volatile 關鍵字可以禁止指令重排 :可以確保instance = new SluggardType2()對應的指令不會重排序 //但是因為對volatile的操作都在Main Memory中,而Main Memory是被所有線程所共享的,這里的代價就是犧牲了性能,無法利用寄存器或Cache private volatile static SluggardType2 instance = null; private SluggardType2(){ } public static SluggardType2 getInstance(){ if(instance == null){ synchronized (SluggardType2.class) { if(instance == null){ instance = new SluggardType2(); } } } return instance; } public void say(){ System.out.println(" 懶漢模式(雙重校驗鎖[不推薦])單例"); } public static void main(String[] args) { SluggardType2.getInstance().say(); } }
package com.xhengxuyuanzhi; /** * @author 微信公眾號:程序員之路 * 博客:http://www.cnblogs.com/chengxuyuanzhilu/ * * 借助內部類實現單利模式: * 特點:既能實現延遲加載,又能實現線程安全 */ public class InnerClassSingleton { /** * 類級的內部類,也就是靜態的成員式內部類,該內部類的實例與外部類的實例沒有綁定關系,而且只有被調用到時才會裝載(裝在過程是由jvm保證線程安全) * ,從而實現了延遲加載 */ private static class SingletonHolder { /** * 靜態初始化器,由JVM來保證線程安全 */ private static InnerClassSingleton instance = new InnerClassSingleton(); } /** * 私有化構造方法 */ private InnerClassSingleton() { } /** * 這個模式的優勢在於:getInstance方法並沒有被同步,並且只是執行一個域的訪問,因此延遲初始化並沒有增加任何訪問成本 */ public static InnerClassSingleton getInstance() { return SingletonHolder.instance; } }
package com.xhengxuyuanzhi; /** * @author 微信公眾號:程序員之路 * 博客:http://www.cnblogs.com/chengxuyuanzhilu/ * * 枚舉實現線程安全的單例模式: * 特點:JVM會保證enum不能被反射並且構造器方法只執行一次 * */ public class EnumSingleton { private EnumSingleton() { } public static EnumSingleton getInstance() { return Singleton.INSTANCE.getInstance(); } private static enum Singleton { INSTANCE; private EnumSingleton singleton; // JVM會保證此方法絕對只調用一次 private Singleton() { singleton = new EnumSingleton(); } public EnumSingleton getInstance() { return singleton; } } }