写点什么

【设计模式】- 创建型模式 - 第 2 章第 1 讲 -【单例模式】

  • 2022-11-01
    北京
  • 本文字数:6868 字

    阅读完需:约 23 分钟

【设计模式】-创建型模式-第2章第1讲-【单例模式】

本章主要介绍创建型模式(Creational Pattern)。创建型模式主要用于处理对象的创建问题。



  • 单例模式

  • 工厂模式

  • 建造者模式

  • 原型模式

  • 对象池模式

 1、单例模式

1.1、为何要用单例模式

单例模式只允许创建一个对象,因此节省内存,加快对象访问速度,因此对象需要被公用的场合适合使用,如多个模块使用同一个数据源连接对象等等。

在单例模式中,活动的单例只有一个实例,对单例类的所有实例化得到的都是相同的一个实例。这样就 防止其它对象对自己的实例化,确保所有的对象都访问一个实例

单例模式具有一定的伸缩性,类自己来控制实例化进程,类就在改变实例化进程上有相应的伸缩性。

提供了对唯一实例的受控访问。

由于在系统内存中只存在一个对象,因此可以 节约系统资源,当需要频繁创建和销毁的对象时单例模式无疑可以提高系统的性能。

允许可变数目的实例。

避免对共享资源的多重占用。

1.2、单例模式(singleton pattern)使我们常用的设计模式。单例,顾名思义,用来保证一个对象只能创建一个实例,并且提供对实例的全局访问方法。

通俗点就是:私有化构造器,提供全局静态访问方法。

示例代码如下:

package com.zhaoyanfei.designpattern.signleton;/** * 单例模式 * @author zhaoYanFei * */public class Singleton { 	private static Singleton instance;	/**	 * 私有构造器	 */	private Singleton() {		System.out.println("Singleton is Instantiated.");	}	/**	 * 全局静态方法访问实例	 * @return	 */	public static Singleton getInstance() {		if(instance == null) 			instance = new Singleton();		return instance;	}		public void doSomething() {		System.out.println("Something is done.");	}}
复制代码


接下来,当我们在代码中使用单例对象时,只需进行简单调用,示例如下:

package com.zhaoyanfei.designpattern.signleton; public class Test { 	public static void main(String[] args) {		//使用单例对象执行相应的操作		Singleton.getInstance().doSomething();	}	}
复制代码

1.3、同步锁单例模式

单例模式的实现代码简单且高效,但是还需要注意一种特殊情况,那就是多线程应用中,可能存在两个线程同时调用 getInstance 方法的情况。

比如:第一个线程首先使用构造器实例化单例对象,这时,第二个线程进来,在第一个线程还完成单例对象的实例化操作的情况下,也会开始实例化单例对象。

当然,上述场景发生的概率很小,但是在实例化单例对象需要较长时间的情况下,发生的可能性就足够高了,所以,我们不能忽略这种并发操作。

解决这个问题也很简单,只需要用关键字(synchronized)来保证线程安全。有两种方式:

1.3.1、获取实例的方法 getInstance() 上添加 synchronized 关键字来保证线程安全。

public static synchronized Singleton getInstance() {
if(instance == null)
instance = new Singleton();
return instance;
}public static Singleton getInstance() { synchronized(Singleton.class) { if(instance == null) instance = new Singleton(); } return instance; }
复制代码

1.3.2、用 synchronized 代码块包装 if(instance==null) 条件,这里需要指定一个对象来提供锁,Singleton.class 对象就起这种作用。

public static Singleton getInstance() {		synchronized(Singleton.class) {			if(instance == null) 				instance = new Singleton();		}		return instance;	}
复制代码

1.4、双重检验锁机制的同步锁单例模式

前面的实现方式目前已经能保证线程安全,但同时带来了延迟。

用来检查实例是否被创建的代码是同步的,那就意味着,此代码块在同一时刻只能被一个线程执行,但是同步锁(locking)只有在实例没有被创建的情况下才起作用。如果单例实例已经创建了,那么正常情况下,任何线程都应该可以用非同步方式获取当前的实例。

只有在单例对象未实例化的情况下,才进入到同步锁,那我们移动线程安全锁:

/**
* 全局静态方法访问实例
* @return
*/
public static Singleton getInstance() {
//1.先判断是否有实例
if(instance == null) {
synchronized(Singleton.class) {
//2.同步代码块内再次检查是否有实例
if(instance == null)
instance = new Singleton();
}
}
return instance;
}
复制代码

这里我们看到判断了两次实例是否存在,因为我们需要保证在 synchronized 代码块中也要进行一次检查。

1.5、无锁的线程安全单例模式

Java 中单例模式的最佳实现形式中,类只会加载一次,通过在声明时直接实例化静态成员属性的方式来保证一个类只有一个实例。

这种实现方式避免了使用同步锁机制和判断实例是否被创建的额外检查。

package com.zhaoyanfei.designpattern.signleton;/** * 无锁线程安全单例模式 * @author zhaoYanFei * */public class LockFreeSingleton { 	/**	 * 实例化静态成员属性	 */	private static final LockFreeSingleton instance = new LockFreeSingleton(); 	private LockFreeSingleton() {		System.out.println("LockFreeSingleton is Instantiated.");	}		public static synchronized LockFreeSingleton getInstance() {		return instance;	}		public void doSomething() {		System.out.println("Something is done.");	}}
复制代码

1.6、提前加载和延迟加载

提前加载和延迟加载的区别,就是实例对象被创建的时机。

如果在应用开始时创建单例实例,那就是提前加载单例模式。

如果在 getInstance 方法首次被调用时才调用单例构造器,那就是延迟加载单例模式。

前面的无锁线程安全单例模式在早期版本的 Java 中被认为是提前加载单例模式,但在最新版本的 Java 中,类只有在使用时才会被加载,所以它也是一种延迟加载模式。

另外,类加载的时机主要取决于 JVM 的实现机制,因而版本之间会有所不同。

目前,Java 语言并没有提供一种创建提前加载单例模式的可靠选项。如果确实需要提前实例化,可以在程序的开始通过调用 getInstance() 方法强制执行,如下面代码所示:

Singleton.getInstance();

1.7、我们所熟知的单例模式的应用

1.7.1、枚举类实现单例模式

《Effective Java》 推荐使用枚举的方式解决单例模式。这种方式解决了最主要的;线程安全、自由串行化、单一实例。

1.7.2、Jdk 源码中单例模式

java.lang.Runtime 使用了单例模式的饿汉式,源码如下:


1.7.3、spring 源码中单例模式

在 Spring 依赖注入 Bean 实例默认是单例的,我们由此展开。bean 可以被定义为两种模式:prototype(原型|多例)和 singleton(单例)。

singleton(单例):只有一个共享的实例存在,所有对这个 bean 的请求都会返回唯一的实例。

prototype(多例):对这个 bean 的每次请求都会创建一个新的 bean 实例,类似于 new。

Spring 中加载单例的过程都是在 BeanFactory 的 getBean() 方法中被定义的,其默认的功能在 AbstractBeanFactory 类中实现,主要包含两个功能。

从缓存中获取单例 Bean。

Bean 的实例中获取对象。

getBean() 方法最终会调用 AbstractBeanFactory 的 doGetBean() 方法,源码如下:

protected <T> T doGetBean(final String name, @Nullable final Class<T> requiredType,                          @Nullable final Object[] args, boolean typeCheckOnly) throws BeansException {    //对传入的beanName稍作修改,防止有一些非法字段,然后提取Bean的Name    final String beanName = transformedBeanName(name);    Object bean;    //直接从缓存中获取单例工厂中的objectFactory单例    Object sharedInstance = getsingleton(beanName);    if (sharedInstance != null && args == null) {        if (logger.isDebugEnabled()) {            if (isSingletonCurrentlyInCreation(beanName)) {                logger.debug("Returning eagerly cached instance of singleton bean '" +                        beanName + "' that is not fully initialized yet - a consequence of a circular reference");            } else {            }        }        //返回对应的实例,从 Bean实例中获取对象        bean = getObjectForBeanInstance(sharedInstance,name,beanName, null);    } else {        ...    }    ...}
复制代码

getBean() 方法不仅处理单例对象的逻辑,还处理原型对象的逻辑。继续看 getSingleton() 方法的代码实现。

getSingleton() 的工作流程:singletonObjects-->earlySingletonObjects-->singletonFactories-->创建单例实例

/*** 单例对象的缓存*/private final Map<String, Object> singletonObjects = new ConcurrentHashMap<>(256);protected Object getSingleton(String beanName, boolean allowEarlyReference) {    //首先通过名字查找这个Bean是否存在    Object singletonObject = this.singletonObjects.get(beanName);    if (singletonObject == null && isSingletonCurrentlyInCreation(beanName)) {        synchronized (this.singletonObjects) {            //查看缓存中是否存在这个Bean            singletonObject = this.earlySingletonObjects.get(beanName);            //如果这个时候的Bean实例还为空并且允许懒加载            if (singletonObjects == 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;}
复制代码

上面代码片段中,synchronized(this.singletonObjects) 是关键,但是前提条件 isSingletonCurrentlyInCreation 的返回值也是 true,也就是这个 Bean 正在被创建。因此,第一次调用 doGetBean() 的时候,getSingleton() 基本上都是返回 null,所以会继续执行 doGetBean() 方法中后面的逻辑。

protected <T> T doGetBean(final String name, @Nullable final Class<T> requiredType,            @Nullable final Object[] args, boolean typeCheckOnly) throws BeansException {    // 获取beanDefinition    final RootBeanDefinition mbd = getMergedLocalBeanDefinition(beanName);                checkMergedBeanDefinition(mbd, beanName, args);                // Guarantee initialization of beans that the current bean depends on.                String[] dependsOn = mbd.getDependsOn();                if (dependsOn != null) {                    for (String dep : dependsOn) {                        if (isDependent(beanName, dep)) {                            throw new BeanCreationException(mbd.getResourceDescription(), beanName,                                    "Circular depends-on relationship between '" + beanName + "' and '" + dep + "'");                        }                        registerDependentBean(dep, beanName);                        try {                            getBean(dep);                        }                        catch (NoSuchBeanDefinitionException ex) {                            throw new BeanCreationException(mbd.getResourceDescription(), beanName,                                    "'" + beanName + "' depends on missing bean '" + dep + "'", ex);                        }                    }                }                // Create bean instance.                if (mbd.isSingleton()) {                    sharedInstance = getSingleton(beanName, () -> {                        try {                            return createBean(beanName, mbd, args);                        }                        catch (BeansException ex) {                            // Explicitly remove instance from singleton cache: It might have been put there                            // eagerly by the creation process, to allow for circular reference resolution.                            // Also remove any beans that received a temporary reference to the bean.                            destroySingleton(beanName);                            throw ex;                        }                    });                    bean = getObjectForBeanInstance(sharedInstance, name, beanName, mbd);                }            }            ...        }}
复制代码

可以看到,在 BeanFactory 中,从 XML 中解析出来的相关配置信息被放在 BeanDefinitionMap 中,通过这个 Map 获取 RootBeanDefinition,然后执行判断语句 if(mbd.isSingleton())。如果是单例的,则接着调用 getSingleton() 的重载方法,传入 mbd 参数。当从缓存中加载单例对象时,会把当前的单例对象在 singletonObjects 中存放一份,这样可以保证在调用 getBean() 方法的时候,singletonObjects 中永远只有一个实例,在获取对象时才会给它分配内存,既保证了内存高效利用,又是线程安全的。

public Object getSingleton(String beanName, ObjectFactory<?> singletonFactory) {    Assert.notNull(beanName, "Bean name must not be null");    synchronized (this.singletonObjects) {    // 直接从缓存中获取单例Bean        Object singletonObject = this.singletonObjects.get(beanName);        if (singletonObject == null) {            if (this.singletonsCurrentlyInDestruction) {                throw new BeanCreationNotAllowedException(beanName,                        "Singleton bean creation not allowed while singletons of this factory are in destruction " +                        "(Do not request a bean from a BeanFactory in a destroy method implementation!)");            }            if (logger.isDebugEnabled()) {                logger.debug("Creating shared instance of singleton bean '" + beanName + "'");            }            beforeSingletonCreation(beanName);            boolean newSingleton = false;            boolean recordSuppressedExceptions = (this.suppressedExceptions == null);            if (recordSuppressedExceptions) {                this.suppressedExceptions = new LinkedHashSet<>();            }            try {                singletonObject = singletonFactory.getObject();                newSingleton = true;            }            catch (IllegalStateException ex) {                // Has the singleton object implicitly appeared in the meantime ->                // if yes, proceed with it since the exception indicates that state.                singletonObject = this.singletonObjects.get(beanName);                if (singletonObject == null) {                    throw ex;                }            }            catch (BeanCreationException ex) {                if (recordSuppressedExceptions) {                    for (Exception suppressedException : this.suppressedExceptions) {                        ex.addRelatedCause(suppressedException);                    }                }                throw ex;            }            finally {                if (recordSuppressedExceptions) {                    this.suppressedExceptions = null;                }                afterSingletonCreation(beanName);            }            if (newSingleton) {                // 在singletonObject中添加要加载的单例                addSingleton(beanName, singletonObject);            }        }        return singletonObject;    }}
复制代码

如此一来,当下次需要这个单例 Bean 时,可以直接从缓存中获取。在 Spring 中创建单例的过程虽然有点绕,但是逻辑非常清楚,就是将需要的对象放在 Map 中,下次需要的时候直接从 Map 中获取即可。

下一节为大家介绍工厂模式,感觉对您有所帮助的话,还望点赞、收藏+关注哦^_^。

发布于: 2022-11-01阅读数: 31
用户头像

人生苦短,拒绝内卷。一心向阳,向阳而生。 2021-08-17 加入

知名互联网公司高级开发工程师,专注于后端Java开发的全栈工程师,一名技术狂热追求者,一直在奔跑从未停止过。 人生苦短,拒绝内卷。我是跟着飞哥学编程,一个一心向阳,向阳而生,努力向上生长的年轻人。

评论 (2 条评论)

发布
用户头像
真详细
2022-11-01 13:59 · 北京
回复
用户头像
写的真不错呀,继续加油!
2022-11-01 12:57 · 北京
回复
没有更多了
【设计模式】-创建型模式-第2章第1讲-【单例模式】_设计模式_跟着飞哥学编程_InfoQ写作社区