在23中设计模式中,单例模式可能是大家认为比较简单的一种设计模式,然而笔者觉得单例模式非常的不简单,而且是独一无二,就像我们在数学中对于ex求导,无论求多少次导数,它都还是ex,多么的坚定和唯一。似乎我们的爱情也应该像单例模式一样,永远都是纯粹且专一的。愿得一人心,白首不相离。
接下来就详细介绍一下这神秘而又专情的单例模式。
单例模式的通俗定义
官方定义:确保一个单例类有且仅有一个实例,并且提供一个全局的公共访问点。
通俗释义:也就是说这个单例类只能有一个自己的实例,简单点来说就是只能new出一个对象,然后提供给全局使用。
通用类图:
单例模式的使用场景
单例模式解决了什么问题呢,简单点说解决了两个问题:
1.保证了一个类只有一个实例,就仅仅new出了一个实例呀,与构造函数不一样的是,构造函数是构造不同的实例对象。
2.为全局提供了该实例的访问节点,也就是说,大家都可以访问,随便访问,我就是我,你再来还是我,就是一样的烟火等着你。
那么为什么要有单例模式呢,到底什么场景下会使用到单例模式呢?我们都知道,单例模式是全局仅有一个实例,所以非常的节省资源,并且某种情况下访问速度也是非常的快,在需要这种公共数据的情况下,单例就能发挥其重要的作用。接下来列举几个实际工作中应用到的场景。
1.在资源共享的情况下,需要频繁的创建销毁资源,为了避免这种操作在性能上的损耗,就可以使用单例模式,例如:日志文件打印,应用配置使用。
2.在控制资源的情况下,如果频繁的创建资源而没有及时回收的话,那么可能会造成系统资源的浪费或者内存泄漏,那么可以采用单例模式避免资源的过度使用便于资源的互相通信。例如:数据库连接池,线程池等。
单例模式的几种形式
不就是提供一个单例吗,不就是为全局提供一个公共访问节点吗,干嘛还这么多形式,到底想怎么玩嘛。单例模式笑着说,这就是我不一样的地方。
- 懒汉式
public class SingletonLH {
private static SingletonLH instance; //太懒了,都没有new对象
private SingletonLH(){}//私有构造方法
//公共全局访问点
public static SingletonLH getInstance(){
if (instance == null){
instance = new SingletonLH();
}
return instance;
}
}
public class SingletonLH {
private static SingletonLH instance; //太懒了,都没有new对象
private SingletonLH(){}//私有构造方法
//公共全局访问点
public static synchronized SingletonLH getInstance(){
if (instance == null){
instance = new SingletonLH();
}
return instance;
}
}
- 饿汉式
public class SingletonEH {
//真是饥渴,提前都给自己new对象了
private static final SingletonEH instance = new SingletonEH();
private SingletonEH(){}//私有构造器
//全局公共访问点
public static SingletonEH getInstance(){
return instance;
}
}
- 双重检查锁
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;
}
}
- 静态内部类
public class SingletonStatic {
//私有构造器
private SingletonStatic (){}
//全局访问点
public static SingletonStatic getInstance() {
return SingleHolder.INSTANCE;
}
//静态内部类
private static class SingleHolder {
private static final SingletonStatic INSTANCE = new SingletonStatic();
}
}
以上的这些代码,我想很多人都能在网上搜到或者自己能亲手编写的出来,我们真正要掌握的是单例模式的核心思想:全局唯一。
单例模式的优缺点
通过上面的单例模式使用场景的介绍,我们可以知道介绍一个模式的优缺点是比较有针对性性的,在某些条件或者场景下其具有不可替代的优点,但是抛开这些条件或者场景往往是并非适用。
优点
- 保证内存唯一性。因为内存中只有一个实例,所以注定了其系统的性能和内存开销很小,这是非常突出的优点。
- 全局公共访问点。对外仅有一个实例访问点,故对于共享资源的访问做到了可靠保障。
- 模式较为简洁。主要是针对代码而言,对于开发者来说只要关注以上两个要点即可开发出单例模式。
缺点
- 产生了和单一职责原则的冲突。为什么会产生冲突,原因是单例模式关注了两个问题,而单一职责要求有且只有一个因素引起类的变更,也就是我我们要唯一,但是没有做到单纯。从代码设计角度的确不符合,耦合了业务逻辑,如果代码改动则会产生职责扩散。
- 扩展性差,因为单例模式不是一个接口,并且只需要全局提供一个,所以除了修改代码别无他法。
- 单元测试比较困难,单例类都是私有的构造方法,所以在没有写好之前,也不能进行Mock来解决,否则就测不出来到底是不是真正的单例。
单例模式经典应用
我们在实际开发当中其实很少用到单例模式,一般在进行框架开发或者基层工具类开发的时候才可能使用到。这里就简单提一下,主要给出相关线索,方便引导和查阅。
- 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;
}
- JDK的Runtime类就使用到了单例模式中的饿汉式。
public class Runtime {
private static Runtime currentRuntime = new Runtime();
public static Runtime getRuntime() {
return currentRuntime;
}
private Runtime() {}
}
- 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.单例模式在哪些地方有应用,工作中是否用到呢?
考察:单例模式的应用场景和在框架中的使用情况。