浅谈Spring框架应用的设计模式(二)——单例模式

前言

Spring是目前Java企业级开发应用广泛的框架之一,其具有强大的生态,且得益于其提供的高扩展能力,能够减少开发成本,提高开发效率。如此优秀的框架,当然离不开各种设计模式,本文主要介绍设计模式中的单例模式在Spring框架中的应用。


一、单例模式介绍

单例模式属于三种设计模式分类中的创建者模式,与工厂模式相同,其都是用于进行对象创建的设计模式。单例模式实例化出来的对象在内存中有且仅有一个,可以实现减少内存的开销,共享共用资源,应用范围广,且属于最简单的一种设计模式。下面逐一介绍单例模式的各种实现。

1、懒汉式与饿汉式

(1)懒汉式

懒汉式又可以称为延迟加载,顾名思义,当需要创建对象的时候才进行实例化操作。

	public class LazySingleton{
		//单例对象声明
		private static LazySingleton lazySingleton = null;
		//私有化构造器
		private LazySingleton(){
		}
		//获取单例对象,当调用该方法时才去判断是否存在对象,不存在实例化一个对象
		public static synchronized LazySingleton getSingletonInstance(){ //需要加锁才能保证线程安全
			if(lazySingleton == null){
				return new LazySingleton();
			}
			return lazySingleton;
		}
	}

(2)饿汉式

饿汉式又可以称为提前加载,与懒汉式相反,它在类初始化的时候就实例化对象。

	public class HungrySingleton{
		//私有化构造器
		private HungrySingleton(){
		}
		//单例对象实例化,在类初始化的时候就实例化该对象
		private static HungrySingleton hungrySingleton = getSingletonInstance();
		
		//获取单例对象
		public static HungrySingleton getSingletonInstance(){
			if(hungrySingleton == null){
				return new HungrySingleton();
			}
			return hungrySingleton;
		}
	}

对比懒汉式和饿汉式,懒汉式需要加锁才能保证线程安全,当多个线程调用此方法时会造成低效率;饿汉式类初始化的时候便进行对象创建,当此对象还没需要被用到时,则会造成资源浪费。因此引申出来第三种单例模式的实现,即双重校验锁。

2、双重校验锁

双重校验锁实际上是在懒汉式的基础上改进的,修改加锁的位置,从而来提高效率。我们在实例化对象的地方加同步锁,当然此时有可能多个线程已经执行到判断逻辑,在等待锁。因此,已实例化完的线程释放锁后,其他线程进来就需要在同步代码块中需要再增加一次判断,所以此方式称之为双重检验锁。

	public class DoubleCheckedLockSingleton{
		//单例对象声明
		private static DoubleCheckedLockSingleton doubleCheckedLockSingleton = null;
		//私有化构造器
		private DoubleCheckedLockSingleton(){
		}
		//获取单例对象,当调用该方法时才去判断是否存在对象,不存在实例化一个对象
		public static DoubleCheckedLockSingleton getSingletonInstance(){ 
			if(doubleCheckedLockSingleton == null){
			    synchronized(DoubleCheckedLockSingleton.class){ //将锁的位置换在实例化对象的地方
			    	if(doubleCheckedLockSingleton == null){ //因为有可能两个线程同时进入到上一层判断,因此需要在同步代码块中再添加一次校验
			    		return new DoubleCheckedLockSingleton();
			    	}
			    } 
			}
			return doubleCheckedLockSingleton;
		}
	}

3、静态内部类

此方法利用了 java的类加载机制来保证初始化创建对象时有且只有一个线程,静态内部类只要没有被主动使用,在外部类初始化时,其属性并不会被初始化,但调用时才会装载静态内部类的属性,从而实例化单例对象。即实现了延迟加载,又不会有线程安全问题,是一种很好的实现方式。

public class StaticSingleton {  
	//静态内部类
    private static class StaticSingletonHolder {  
   		private static final StaticSingleton INSTANCE = new StaticSingleton();  
    }  
    //私有化构造器
    private StaticSingleton (){
    }  
    //通过内部类来获取单例对象
    public static final StaticSingleton getInstance() {  
        return StaticSingletonHolder.INSTANCE;  
    }  
}

4、枚举方式

枚举方式可以算是实现单例模式的最佳方法了。它更简洁,同时也能避免多线程同步问题,并且自动支持序列化机,绝对防止多次实例化。

public enum EnumSingleton {  
    INSTANCE;  
    public void doMethod() {  
    	//do...
    }  
}

二、Spring框架中单例模式的应用

1、BeanFactory接口中默认单例Bean

Spring框架中的BeanDefinition会有一个作用域scope,当我们没有进行设置时,Bean工厂根据其默认创建单例Bean,因此Spring框架中我们所用到的Bean通常都是单例的。

public interface FactoryBean<T> {
	String OBJECT_TYPE_ATTRIBUTE = "factoryBeanObjectType";
	@Nullable
	T getObject() throws Exception;
	@Nullable
	Class<?> getObjectType();
	/**************isSingleton 默认为true************************/
	default boolean isSingleton() {
		return true;
	}
}

从上面代码的可以看到,在BeanFactory这个Spring容器的顶层接口中,isSingleton方法默认返回true。

2、Spring Bean单例模式的设计

Spring Bean采用了双重校验锁以及ConcurrentHashMap作为容器实现了单例设计,并且通过三级缓存解决循环依赖的问题。
我们来看下Spring Bean的创建方法,在AbstractBeanFactory类中。

	protected <T> T doGetBean(
			String name, @Nullable Class<T> requiredType, @Nullable Object[] args, boolean typeCheckOnly)
			throws BeansException {
		String beanName = transformedBeanName(name);
		Object beanInstance;
		//先判断容器中是否存在
		Object sharedInstance = getSingleton(beanName);
		if (sharedInstance != null && args == null) {
			//...省略
			beanInstance = getObjectForBeanInstance(sharedInstance, name, beanName, null);
		}
		else {
			//...省略 判断BeanDefinition 是否存在...
			try {
				if (requiredType != null) {
					beanCreation.tag("beanType", requiredType::toString);
				}
				RootBeanDefinition mbd = getMergedLocalBeanDefinition(beanName);
				checkMergedBeanDefinition(mbd, beanName, args);
				//...省略
				}
				//进行Bean的实例化
				if (mbd.isSingleton()) {
					//调用DefaultSingletonBeanRegistry的getSingleton方法,使用lambda表达式
					sharedInstance = getSingleton(beanName, () -> {
						try {
							return createBean(beanName, mbd, args);
						}
						catch (BeansException ex) {
						}
					});
				}
				//...省略

可以看到在创建Bean之前会先去判断容器中是否存在Bean对象,存在的话直接获取,代码如下:

    //一级缓存
	private final Map<String, Object> singletonObjects = new ConcurrentHashMap<>(256);

	//二级缓存
	private final Map<String, Object> earlySingletonObjects = new ConcurrentHashMap<>(16);
	
	//三级缓存
	private final Map<String, ObjectFactory<?>> singletonFactories = new HashMap<>(16);

	//获取单例Bean 一级缓存 -> 二级缓存 -> 三级缓存
	protected Object getSingleton(String beanName, boolean allowEarlyReference) {
			Object singletonObject = this.singletonObjects.get(beanName);
			if (singletonObject == null && isSingletonCurrentlyInCreation(beanName)) {
				singletonObject = this.earlySingletonObjects.get(beanName);
				if (singletonObject == null && allowEarlyReference) {
					synchronized (this.singletonObjects) { //同步锁,解决Bean存在于三级缓存HashMap中的线程安全问题
						singletonObject = this.singletonObjects.get(beanName);
						if (singletonObject == null) {
							singletonObject = this.earlySingletonObjects.get(beanName);
							if (singletonObject == null) {
								ObjectFactory<?> singletonFactory = this.singletonFactories.get(beanName);
								if (singletonFactory != null) {
									singletonObject = singletonFactory.getObject();
									this.earlySingletonObjects.put(beanName, singletonObject);
									this.singletonFactories.remove(beanName);
								}
							}
						}
					}
				}
			}
			return singletonObject;
		}

当三级缓存中都不存在相应的Bean对象时,则进行Bean对象的创建,调用DefaultSingletonBeanRegistry的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) {
				if (this.singletonsCurrentlyInDestruction) {
					//...省略
				}
				//...省略
				boolean newSingleton = false;
				boolean recordSuppressedExceptions = (this.suppressedExceptions == null);
				if (recordSuppressedExceptions) {
						//...省略
				}
				try {
					//singletonFactory为函数式接口,由上面可知此方法会去创建Bean 会调用createBean方法
					singletonObject = singletonFactory.getObject();
					newSingleton = true;
				}
				catch (IllegalStateException ex) {
					//...省略
				}
				catch (BeanCreationException ex) {
					//...省略
				}
				finally {
					//...省略
				}
				if (newSingleton) {
					//添加单例Bean进入容器中
					addSingleton(beanName, singletonObject);
				}
			}
			return singletonObject;
		}
	}

我们来看下addSingleton方法:

	protected void addSingleton(String beanName, Object singletonObject) {
			synchronized (this.singletonObjects) { //同步锁
				this.singletonObjects.put(beanName, singletonObject); //添加对象进入一级缓存
				this.singletonFactories.remove(beanName); //移除三级缓存对象
				this.earlySingletonObjects.remove(beanName); //移除二级缓存对象
				this.registeredSingletons.add(beanName);
			}
		}

总结

本文主要介绍单例模式的几种实现方法以及Spring框架中单例Bean的实现,Spring框架的单例模式实现采用了更加符合本框架运行的方式来实现单例模式的设计,通过类似双重校验锁方式并配合ConcurrentHashMap这个线程安全的HashMap,来完成Bean的单例创建,使得默认生成的Bean在容器中有且仅有一个,也保证了在创建过程中内存有且仅有一个对象。

  • 1
    点赞
  • 9
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值