由Spring框架中的单例模式想到的
单例模式是一种常用的软件设计模式。在它的核心结构中只包含一个被称为单例的特殊类。通过单例模式可以保证系统中一个类只有一个实例
注: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 微信公众号:程序员之路
* \
*
* 饿汉式单例模式
* 特点:可以通过反射机制攻击;线程安全[多个类加载器除外]。
*/
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 微信公众号:程序员之路
* \
*
* 懒汉模式单例
* 特点:延时加载;线程不安全,多线程下不能正常工作;
*/
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 微信公众号:程序员之路
* \
*
* 懒汉模式(双重校验锁[不推荐])单例
*/
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 微信公众号:程序员之路
*
*
* 借助内部类实现单利模式:
* 特点:既能实现延迟加载,又能实现线程安全
*/
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 微信公众号:程序员之路
*
*
* 枚举实现线程安全的单例模式:
* 特点: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;
}
}
}