设计模式——单例模式

本文详细介绍了Java中的单例模式,包括饿汉式、懒汉式、内部类和枚举四种实现方式,以及它们各自的优缺点。同时,讨论了单例模式在Spring框架中的应用,如BeanFactory接口默认的单例行为和SpringBean的三级缓存实现。此外,还提到了线程池和在线人数计数器等实际应用场景,强调了单例模式在资源管理和效率提升上的重要作用。
摘要由CSDN通过智能技术生成

单例模式

介绍

单例模式主要是为了避免因为创建了多个实例造成资源的浪费,且多个实例由于多次调用容易导致结果出现错误,而使用单例模式能够保证整个应用中有且只有一个实例。

定义:只需要三步就可以保证对象的唯一性

(1) 不允许其他程序用new对象
(2) 在该类中创建对象
(3) 对外提供一个可以让其他程序获取该对象的方法

对比定义:

(1) 私有化该类的构造函数
(2) 通过new在本类中创建一个本类对象
(3) 定义一个公有的方法,将在该类中所创建的对象返回

单例模式实现

1 单例模式的饿汉式

public class Singleton {
 
	private static Singleton instance=new Singleton();
	private Singleton(){};
	public static Singleton getInstance(){
		return instance;
	}
}

访问方式

Singleton instance = Singleton.getInstance();

优点:从它的实现中我们可以看到,这种方式的实现比较简单,在类加载的时候就完成了实例化,避免了线程的同步问题。

缺点:由于在类加载的时候就实例化了,所以没有达到Lazy Loading(懒加载)的效果,也就是说可能我没有用到这个实例,但是它也会加载,会造成内存的浪费(但是这个浪费可以忽略,所以这种方式也是推荐使用的)

2 单例模式懒汉式双重校验锁

public class Singleton {
	/**
	 * 懒汉式变种,属于懒汉式中最好的写法,保证了:延迟加载和线程安全
	 */
	private static Singleton instance=null;
	
	private Singleton() {};
	
	public static Singleton getInstance(){
		 if (instance == null) {  
	          synchronized (Singleton.class) {  
	              if (instance == null) {  
	            	  instance = new Singleton();  
	              }  
	          }  
	      }  
	      return instance;  
	}
}

访问方式

Singleton instance = Singleton.getInstance();

进行了两次if (instance== null)检查,这样就可以保证线程安全了。这样,实例化代码只用执行一次,后面再次访问时,判断if (instance== null),直接return实例化对象。

优点:线程安全;延迟加载;效率较高

3 内部类

public class Singleton{
 
	private Singleton() {};
	
	private static class SingletonHolder{
		private static Singleton instance=new Singleton();
	} 
	
	public static Singleton getInstance(){
		return SingletonHolder.instance;
	}
}

访问方式

Singleton instance = Singleton.getInstance();

这种方式跟饿汉式方式采用的机制类似,但又有不同。两者都是采用了类装载的机制来保证初始化实例时只有一个线程。不同的地方在饿汉式方式是只要Singleton类被装载就会实例化,没有Lazy-Loading的作用,而静态内部类方式在Singleton类被装载时并不会立即实例化,而是在需要实例化时,调用getInstance方法,才会装载SingletonHolder类,从而完成Singleton的实例化。类的静态属性只会在第一次加载类的时候初始化,所以在这里,JVM帮助我们保证了线程的安全性,在类进行初始化时,别的线程是无法进入的。

优点:避免了线程不安全,延迟加载,效率高。

4 枚举

public enum SingletonEnum {
	
	 INSTANCE; 
	 public void method(){
	 }
}

访问方式

SingletonEnum.INSTANCE.method();

单例模式在项目框架中的应用

在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);
		}
	}

在线程池中的应用


public class ThreadPoolSingle {
	
	private static ExecutorService fixedThreadPool = null;
 
	private ThreadPoolSingle() {}
	
	public static synchronized ExecutorService getInstance() {
		if(fixedThreadPool == null) {
			fixedThreadPool = Executors.newFixedThreadPool(getPoolSizeNumber());
		}
		return fixedThreadPool;
	}
}

在线人数计数器

public class Counter {
   
    private static class CounterHolder{
        private static final Counter counter = new Counter();
    }
 
    private Counter(){
        System.out.println("init...");
    }
 
    public static final Counter getInstance(){
        return CounterHolder.counter;
    }
 
    private AtomicLong online = new AtomicLong();
 
    public long getOnline(){
        return online.get();
    }
 
    public long add(){
        return online.incrementAndGet();
    }
}    

配置文件访问类

项目中经常需要一些环境相关的配置文件,比如短信通知相关的、邮件相关的。比如 properties 文件,这里就以读取一个properties 文件配置为例,如果你使用的 Spring ,可以用 @PropertySource 注解实现,默认就是单例模式。如果不用单例的话,每次都要 new 对象,每次都要重新读一遍配置文件,很影响性能,如果用单例模式,则只需要读取一遍就好了。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值