单例模式
介绍
单例模式主要是为了避免因为创建了多个实例造成资源的浪费,且多个实例由于多次调用容易导致结果出现错误,而使用单例模式能够保证整个应用中有且只有一个实例。
定义:只需要三步就可以保证对象的唯一性
(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 对象,每次都要重新读一遍配置文件,很影响性能,如果用单例模式,则只需要读取一遍就好了。