设计模式之单例模式
单例模式
所谓单例,就是整个程序有且仅有一个实例。该类负责创建自己的对象,同时确保只有一个对象被创建。在Java,一般常用在工具类的实现或创建对象需要消耗资源。
单例模式的特点
- 构造方法私有化
- 指向自己实例的私有静态引用
- 以自己实例为返回值的公共静态方法
为什么需要单例模式
许多时候,整个系统只需要拥有一个的全局对象,这样有利于我们协调系统整体的行为。比如在某个服务器程序中,该服务器的配置信息存放在一个文件中,这些配置数据由一个单例对象统一读取,然后服务进程中的其他对象再通过这个单例对象获取这些配置信息,显然,这种方式简化了在复杂环境下的配置管理。
特别地,在计算机系统中,线程池、缓存、日志对象、对话框、打印机、显卡的驱动程序对象常被设计成单例。事实上,这些应用都或多或少具有资源管理器的功能。例如,每台计算机可以有若干个打印机,但只能有一个 Printer Spooler(单例) ,以避免两个打印作业同时输出到打印机中。再比如,每台计算机可以有若干通信端口,系统应当集中 (单例)管理这些通信端口,以避免一个通信端口同时被两个请求同时调用。总之,选择单例模式就是为了避免不一致状态,避免政出多头。
综上所述,单例模式就是为确保一个类只有一个实例,并为整个系统提供一个全局访问点的一种方法。
单例模式的实现方法
饿汉模式
饿汉式在类创建的同时就已经创建好一个静态的对象供系统使用,以后不再改变,所以天生是线程安全的。比较常用,但容易产生垃圾,因为一开始就初始化。一般用来初始化项目的时候加载配置文件等。
public class Singleton {
private static Singleton instance = new Singleton();
private Singleton (){}
public static Singleton getInstance() {
return instance;
}
}
懒汉模式
懒汉模式(线程不安全)
线程不安全,延迟初始化,严格意义上不是不是单例模式
public class Singleton {
//私有实例属性
private static Singleton instance;
//私有构造方法
private Singleton (){}
//获取实例方法
public static Singleton getInstance() {
if (instance == null) {
instance = new Singleton();
}
return instance;
}
}
懒汉模式(线程安全)(不推荐)
在getInstance方法调用上加了同步,虽然线程安全了,但是每次都要同步,会影响性能,毕竟99%的情况下是不需要同步的。
public class Singleton {
private static Singleton singleton;
private Singleton(){}
// 使用 synchronized 修饰,临界资源的同步互斥访问
public static synchronized Singleton getInstance(){
if (singleton == null) {
singleton = new Singleton();
}
return singleton;
}
}
懒汉模式(线程安全)(不推荐)
和上一种类似,每次调用都会加锁,效率不高
public class Singleton {
private volatile static Singleton singleton;
private Singleton (){}
public static Singleton getInstance() {
//如果没有实例,则锁住当前类
synchronized (Singleton.class) {
//再次判断有无实例,加锁之前可能被其他线程先创建实例
if (singleton == null) {
singleton = new Singleton();
}
}
return singleton;
}
}
懒汉模式(线程安全)——双重检查锁定(推荐)
在getInstance中做了两次null检查,确保了只有第一次调用单例的时候才会做同步,这样也是线程安全的,同时避免了每次都同步的性能损耗。
请注意这里的关键词 volatile ,文末会说明为什么要加
public class Singleton {
private volatile static Singleton singleton;
private Singleton (){}
public static Singleton getInstance() {
//先判断有没有实例化
if (singleton == null) {
//如果没有实例,则锁住当前类
synchronized (Singleton.class) {
//再次判断有无实例,加锁之前可能被其他线程先创建实例
if (singleton == null) {
singleton = new Singleton();
}
}
}
return singleton;
}
}
懒汉模式(静态内部类)
只有第一次调用getInstance方法时,虚拟机才加载 Inner 并初始化instance ,只有一个线程可以获得对象的初始化锁,其他线程无法进行初始化,保证对象的唯一性。目前此方式是所有单例模式中最推荐的模式,但具体还是根据项目选择。
public class Singleton {
private Singleton(){
}
public static Singleton getInstance(){
return Inner.instance;
}
private static class Inner {
private static final Singleton instance = new Singleton();
}
}
特殊的单例模式(枚举)
默认枚举实例的创建是线程安全的,并且在任何情况下都是单例。实际上
枚举类隐藏了私有的构造器。
枚举类的域 是相应类型的一个实例对象
public enum Singleton {
INSTANCE;
}
枚举实例在日常开发是很少使用,就是很简单以导致可读性较差。
单例模式的优缺点
优点
- 由于单例模式在内存中只有一个实例,减少了内存开支,特别是一个对象需要频繁地创建、销毁时,而且创建或销毁时性能又无法优化,单例模式的优势就非常明显。
- 由于单例模式只生成一个实例,所以减少了系统的性能开销,当一个对象的产生需要比较多的资源时,比如读取配置、产生其他依赖对象时,则可以通过在应用启动时直接产生一个单例对象,然后用永久驻留内存的方式来解决。
- 单例模式可以避免对资源的多重占用,例如一个写文件操作,由于只有一个实例存在内存中,避免对同一个资源文件的同时写操作。
- 单例模式可以在系统设置全局的访问点,优化和共享资源访问,例如,可以设计一个单例类,负责所有数据表的映射处理。
缺点
- 单例模式一般没有接口,扩展很困难,若要扩展,除了修改代码基本上没有第二种途径可以实现。
- 单例对象如果持有Context,那么很容易引发内存泄露,此时需要注意传给单例对象的Context最好是Application Context。
volatile
instance = new Singleton() 在JVM中会发生什么?
1. new一个对象是非原子操作的,会分为如下三个步骤
memory = allocate(); //1:分配对象的内存空间
ctorInstance(memory); //2:初始化对象
singleton = memory; //3:使singleton指向刚分配的内存地址
2. 实际上这个过程可能是无序的,有可能先分配内存地址然后初始化对象
memory = allocate(); //1:分配对象的内存空间
singleton = memory; //3:使singleton指向刚分配的内存地址,此时singleton != null
ctorInstance(memory); //2:初始化对象
重排序情景再现
在多线程的情况下,假设有两个线程,
- 线程1进入
getInstance()
方法; - 此时
singleton == null
,线程1进入synchronized
代码块; - 当给
singleton
分配内存地址后,并未完成初始化,此时singleton != null
; - 线程2进入
getInstance()
方法, 此时singleton != null
,return
一个没有初始化的对象; - 线程1完成初始化;
一旦出现这种情况,线程2得到了没有完成初始化的对象,有可能导致灾难性的后果。我们只需使用volatile
关键字修饰单例引用就可以避免上述灾难。