单例模式详解

单例模式

1.单例模式的设计意图是什么?它要解决一个什么问题?什么时候可以使用它?

2.它是如何解决问题的?

3.结构图是怎样的,有哪些关键代码

4.能够想到这个设计模式的两个应用实例,一个生活中,一个软件中

5.这个模式的优缺点是什么?在使用时要注意什么

单例模式的设计意图

单例模式设计的意图是 为了节约系统资源,确保当前的类在进程的生命周期中只会存在一个实例,也就是说当前类的构造方法在进程的生命周期中只会被调用一次。

如果有些类占用资源太多,或者如果有多个实例会难于管理,比方说线程池,数据库连接池之类的,这些需要使用到单例模式

单例模式是如何解决问题的?

因为在进程中只存在唯一的一个实例,所以在任意时刻通过该类拿到的永远是同一个对象,实现了资源的复用

单例模式的实现与结构图

目前实现单例模式的方式著有要三种 :静态内部类(推荐),枚举(一般),双重检查锁 DCL(一般)

要实现单例要做的几件事

  1. 构造器必需私有化,也就是说外部不能调用到自己的构造方法

  2. 拥有一个全局的私有静态变量

  3. 提供一个访问该私有变量的静态方法

静态内部类的实现方式

静态内部类实现单例的原理是 利用 JVM 的 类加载机制实现的。类加载时会在初始化阶段为所有的静态变量赋值。

其典型的代码如下:

1
2
3
4
5
6
7
8
9
private static class SingleHodler{
private static final xxx instance = new xxx();
};

private xxx{}

public xxx getInstance(){
return SingleHodler.instance;
}

枚举

枚举没啥好说的,其实现原理与静态内部类一样

但是枚举要注意一个反序列化生成对象的问题,通过序列化可以将一个对象写入到磁盘,然后要使用的时候通过反序列化可以得到一个新的对象,这就有可能使得单例模式失效。

反序列化提供了一个很特别的钩子函数,类中具有一个私有的被实例化的方法 readResolve ,我们可以通过控制该方法控制对象的反序列化,要杜绝反序列化生成新对象可以实现如下方法:

1
2
3
private Object readResolve(){
return instance;//返回自己创建的单例
}

这个方法是底层通过反射调用的

双重检查锁(double ckeck lock)

双重检查锁的目的是为了解决多线程下单例的问题。其使用的volatile关键字在JDK1.5以后才有,并且volatile的禁止指令重排机制会屏蔽虚拟机的一些优化操作,因此并不是单例的一种完美实现方式。

如果static变量不添加volatile关键字是有可能导致单例失效的,其具体原因如下:

一个对象的创建过程由这么几步组成,1.分配内存 2.初始化 3.将instance对象指向分配的内存空间。那么由于JVM允许在执行时进行指令优化,如果不添加volatile关键字,那么2与3的执行顺序是不确定的,有可能是1-2-3,也有可能是1-3-2.如果是1-3-2,3先执行,2未执行,如果此时切换线程B,此时instance在线程A中已经执行过3了,所以instance不为空,线程B会直接取走instance,在使用的时候就会报错,这就是DCL失效的问题

单例模式的优缺点

优点:

  1. 提供了唯一对象的受控访问,可以严格控制用户的访问

  2. 由于内存中只存在一个对象,节约了系统资源

缺点:

  1. 由于单例模式中没有抽象层,所以扩展会很困难

  2. 单例类的职责过重,在一定程度上违反了单一职责原则

  3. 在很多面向对象语言中都提供了垃圾自动回收机制,如果实例共享对象长时间没有被引用,会被收集器回收,再次使用时会重新创建对象,此时有可能会导致共享对象的状态丢失

目前推荐使用的是静态内部类与枚举

为了防止反射与反序列化重新创建对象由如下解决方式:如果是反射,可以在构造函数中判断当前实例是否存在,如果已经存在则抛出异常,反序列化前面已经讲过了不再重复。

-------------本文结束感谢您的阅读-------------