文章目录
单例模式
简介
单例模式(Singleton Pattern)也叫单子模式,是一种常用的软件设计模式,属于创建型模式的一种。在应用这个模式时,单例对象的类必须保证只有一个实例存在。
实现思路
- 一个类能返回对象一个引用(永远是同一个)和一个获得该实例的方法(必须是静态方法,通常使用getInstance这个名称)
- 当我们调用这个方法时,如果类持有的引用不为空就返回这个引用,如果类保持的引用为空就创建该类的实例并将实例的引用赋予该类保持的引用
- 同时我们还将该类的构造函数定义为私有方法,这样其他处的代码就无法通过调用该类的构造函数来实例化该类的对象,只有通过该类提供的静态方法来得到该类的唯一实例。
实现方式
只记录线程安全的实现方式:
懒汉式,方法上加synchronized 的方式
必须加锁 synchronized 才能保证单例,但加锁会影响效率。
public class Singleton {
private static Singleton instance;
private Singleton (){}
public static synchronized Singleton getInstance() {
if (instance == null) {
instance = new Singleton();
}
return instance;
}
}
双检锁/双重校验锁(DCL,即 double-checked locking)
这种方式采用双锁机制,安全且在多线程情况下能保持高性能。
public class Singleton {
//增加volatile禁止对象创建的指令重排
private volatile static Singleton singleton;
private Singleton (){}
public static Singleton getSingleton() {
if (singleton == null) {
synchronized (Singleton.class) {
if (singleton == null) {
singleton = new Singleton();
}
}
}
return singleton;
}
}
增加volatile的原因:
singleton = new Singleton();
并非是原子性操作,会涉及到指令重排。在java中创建一个对象并非是一个原子操作,可以被分解成三行伪代码:
memory = allocate(); //1:分配对象的内存空间
ctorInstance(memory); //2:初始化对象
instance = memory; //3:设置instance指向刚分配的内存地址
因为第二步和第三步没有直接关联,所以可能触发指令重排。经过指令重排后,可能会变成:
memory = allocate(); //1:分配对象的内存空间
instance = memory; //3:设置instance指向刚分配的内存地址
ctorInstance(memory); //2:初始化对象
当有多个线程访问时,可能会出现的问题:
时间 | 线程A | 线程B |
---|---|---|
t1 | 分配对象的内存空间 | |
t2 | 设置instance指向刚分配的内存地址 | |
t3 | 判断singleton不为空 | |
t4 | 返回一个没有初始化的instance对象 | |
t5 | 初始化对象 | |
t6 | 返回singleton对象 |
通过如上步骤可知,线程B可能会返回一个没有初始化的instance对象。
之所以要增加volatile关键字,是因为它能阻止指令重排。
枚举
这是实现单例模式的最佳方法。它更简洁,自动支持序列化机制,绝对防止多次实例化。枚举类实现单例模式是 effective java 作者极力推荐的单例实现模式,因为枚举类型是线程安全的,并且只会装载一次,设计者充分的利用了枚举的这个特性来实现单例模式,枚举的写法非常简单,而且枚举类型是所用单例实现中唯一一种不会被破坏的单例实现模式。
public class SingletonExample {
private SingletonExample() {
}
public static SingletonExample getInstance() {
return Singleton.INSTANCE.getSingleton();
}
private enum Singleton {
INSTANCE;
private SingletonExample singleton;
Singleton() {
this.singleton = new SingletonExample();
}
public SingletonExample getSingleton() {
return singleton;
}
}
}
饿汉式
由于类加载时就初始化,浪费内存。
public class Singleton {
private static Singleton instance = new Singleton();
private Singleton (){}
public static Singleton getInstance() {
return instance;
}
}
破坏单例模式的方法及解决办法
- 除枚举方式外, 其他方法都会通过反射的方式破坏单例,反射是通过调用构造方法生成新的对象,所以如果我们想要阻止单例破坏,可以在构造方法中进行判断,若已有实例, 则阻止生成新的实例,解决办法如下:
private SingletonObject1(){
if (instance !=null){
throw new RuntimeException("实例已经存在,请通过 getInstance()方法获取");
}
}
- 如果单例类实现了序列化接口Serializable, 就可以通过反序列化破坏单例,所以我们可以不实现序列化接口,如果非得实现序列化接口,可以重写反序列化方法readResolve(), 反序列化时直接返回相关单例对象。
public Object readResolve() throws ObjectStreamException {
return instance;
}
参考:
https://blog.csdn.net/lkxsnow/article/details/104258097
https://www.runoob.com/design-pattern/singleton-pattern.html
https://juejin.im/post/6844903925783461896