单例模式:她真的很简单吗?

在23中设计模式中,单例模式可能是大家认为比较简单的一种设计模式,然而笔者觉得单例模式非常的不简单,而且是独一无二,就像我们在数学中对于ex求导,无论求多少次导数,它都还是ex,多么的坚定和唯一。似乎我们的爱情也应该像单例模式一样,永远都是纯粹且专一的。愿得一人心,白首不相离。

接下来就详细介绍一下这神秘而又专情的单例模式。

单例模式的通俗定义

官方定义:确保一个单例类有且仅有一个实例,并且提供一个全局的公共访问点。
通俗释义:也就是说这个单例类只能有一个自己的实例,简单点来说就是只能new出一个对象,然后提供给全局使用。
通用类图
单例模式通用类图

单例模式的使用场景

单例模式解决了什么问题呢,简单点说解决了两个问题:
1.保证了一个类只有一个实例,就仅仅new出了一个实例呀,与构造函数不一样的是,构造函数是构造不同的实例对象。
2.为全局提供了该实例的访问节点,也就是说,大家都可以访问,随便访问,我就是我,你再来还是我,就是一样的烟火等着你。
那么为什么要有单例模式呢,到底什么场景下会使用到单例模式呢?我们都知道,单例模式是全局仅有一个实例,所以非常的节省资源,并且某种情况下访问速度也是非常的快,在需要这种公共数据的情况下,单例就能发挥其重要的作用。接下来列举几个实际工作中应用到的场景。
1.在资源共享的情况下,需要频繁的创建销毁资源,为了避免这种操作在性能上的损耗,就可以使用单例模式,例如:日志文件打印,应用配置使用。
2.在控制资源的情况下,如果频繁的创建资源而没有及时回收的话,那么可能会造成系统资源的浪费或者内存泄漏,那么可以采用单例模式避免资源的过度使用便于资源的互相通信。例如:数据库连接池,线程池等。

单例模式的几种形式

不就是提供一个单例吗,不就是为全局提供一个公共访问节点吗,干嘛还这么多形式,到底想怎么玩嘛。单例模式笑着说,这就是我不一样的地方。

  1. 懒汉式
/**
 * @Author: Max
 * @Description:懒汉式-线程不安全
 */
public class SingletonLH {
    private static SingletonLH instance; //太懒了,都没有new对象
    
    private SingletonLH(){}//私有构造方法

    //公共全局访问点
    public static SingletonLH getInstance(){

        if (instance == null){
            instance = new SingletonLH();
        }
        return instance;
    }
}
/**
 * @Author: Max
 * @Date: 2020-08-25 22:33
 * @Description:懒汉式-线程安全
 */
public class SingletonLH {

    private static SingletonLH instance; //太懒了,都没有new对象
    
    private SingletonLH(){}//私有构造方法

    //公共全局访问点
    public static synchronized SingletonLH getInstance(){
        if (instance == null){
            instance = new SingletonLH();
        }
        return instance;
    }
}
  1. 饿汉式
/**
 * @Author: Max
 * @Description:饿汉式-天生线程安全
 */
public class SingletonEH {
    //真是饥渴,提前都给自己new对象了
    private static final SingletonEH instance = new SingletonEH();

    private SingletonEH(){}//私有构造器

    //全局公共访问点
    public static SingletonEH getInstance(){
        return instance;
    }
}
  1. 双重检查锁
/**
 * @Author: Max
 * @Description:双重检查锁
 */
public class SingletonDL {
    private static SingletonDL instance;

    private SingletonDL(){}//私有构造方法

    //公共全局访问点
    public static SingletonDL getInstance(){
        if (instance == null){
            synchronized (SingletonDL.class){
                if (instance == null){
                    instance = new SingletonDL();
                }
            }
        }
        return instance;
    }
}
  1. 静态内部类
/**
 * @Author: Max
 * @Description:静态内部类
 */
public class SingletonStatic {

    //私有构造器
    private SingletonStatic (){}

    //全局访问点
    public static SingletonStatic getInstance() {
        return SingleHolder.INSTANCE;
    }

    //静态内部类
    private static class SingleHolder {
        private static final SingletonStatic INSTANCE = new SingletonStatic();
    }
}

以上的这些代码,我想很多人都能在网上搜到或者自己能亲手编写的出来,我们真正要掌握的是单例模式的核心思想:全局唯一。

单例模式的优缺点

通过上面的单例模式使用场景的介绍,我们可以知道介绍一个模式的优缺点是比较有针对性性的,在某些条件或者场景下其具有不可替代的优点,但是抛开这些条件或者场景往往是并非适用。
优点

  • 保证内存唯一性。因为内存中只有一个实例,所以注定了其系统的性能和内存开销很小,这是非常突出的优点。
  • 全局公共访问点。对外仅有一个实例访问点,故对于共享资源的访问做到了可靠保障。
  • 模式较为简洁。主要是针对代码而言,对于开发者来说只要关注以上两个要点即可开发出单例模式。

缺点

  • 产生了和单一职责原则的冲突。为什么会产生冲突,原因是单例模式关注了两个问题,而单一职责要求有且只有一个因素引起类的变更,也就是我我们要唯一,但是没有做到单纯。从代码设计角度的确不符合,耦合了业务逻辑,如果代码改动则会产生职责扩散。
  • 扩展性差,因为单例模式不是一个接口,并且只需要全局提供一个,所以除了修改代码别无他法。
  • 单元测试比较困难,单例类都是私有的构造方法,所以在没有写好之前,也不能进行Mock来解决,否则就测不出来到底是不是真正的单例。

单例模式经典应用

我们在实际开发当中其实很少用到单例模式,一般在进行框架开发或者基层工具类开发的时候才可能使用到。这里就简单提一下,主要给出相关线索,方便引导和查阅。

  1. Spring框架中的AbstractFactoryBean对于单例模式的经典应用。
public abstract class AbstractFactoryBean<T>
		implements FactoryBean<T>, BeanClassLoaderAware, BeanFactoryAware, InitializingBean, DisposableBean {
	private boolean singleton = true;
	@Nullable
	private ClassLoader beanClassLoader = ClassUtils.getDefaultClassLoader();
	@Nullable
	private BeanFactory beanFactory;
	private boolean initialized = false;
	@Nullable
	private T singletonInstance;
	@Nullable
	private T earlySingletonInstance;
}
  1. JDK的Runtime类就使用到了单例模式中的饿汉式。
public class Runtime {
    private static Runtime currentRuntime = new Runtime();
    public static Runtime getRuntime() {
        return currentRuntime;
    }
    /** Don't let anyone else instantiate this class */
    private Runtime() {}
  }
  1. Mybatis中的ErrorContext也使用到了单例模式。
public class ErrorContext {
    private static final String LINE_SEPARATOR = System.getProperty("line.separator", "\n");
    private static final ThreadLocal<ErrorContext> LOCAL = new ThreadLocal();
    private ErrorContext() {
    }
    public static ErrorContext instance() {
        ErrorContext context = (ErrorContext)LOCAL.get();
        if (context == null) {
            context = new ErrorContext();
            LOCAL.set(context);
        }
        return context;
    }
 }

经典使用其实非常多,我们要掌握这些实际的应用,就需要对于这些代码设计在实际项目中的作用和原理进行剖析。

常见的面试灵魂拷问

1.手写一个单例模式,并谈一谈对于单例模式的理解。
考察:单例模式的几种写法,线程安全对比,优缺点的考问。
2.单例模式在哪些地方有应用,工作中是否用到呢?
考察:单例模式的应用场景和在框架中的使用情况。

猜你喜欢

转载自blog.csdn.net/weixin_30484149/article/details/108229252