单例模式是软件开发过程中经常用到的一种设计模式,但如果优雅的实现单例模式,并不是一个简单的问题。
常规加锁模式
单例模式需要注意的问题,就是多个线程同时请求的时候,只允许一个线程进行单例的创建和初始化,那么 显然可以想到使用 synchronize
关键字进行约束。但是synchronize
关键字并不能保证方法内的 指令不被重排序,比如创建对象的时候有三个步骤:
- 创建对象
- 初始化对象
- 返回对象
正常情况下的指令执行顺序是 1 2 3,如果发生了指令重排,那么可能的结果是 1 3 2,也就是在 未初始化的情况下就返回了对象,所以还需要增加 volatile
关键字约束单例变量,以保证不会 发生指令重排。
public class SingletonDoubleCheck {
/** 保存该类的唯一-实例,使用volatile关键字修饰instance */
private static volatile SingletonDoubleCheck instance;
/** 私有构造器使其他类无法直接通过new创建该类的实例 */
private SingletonDoubleCheck() {
//什么也不做
}
public static SingletonDoubleCheck getInstance() {
if (null == instance) {// 操作①:第1次检查
synchronized (SingletonDoubleCheck.class) {
if (null == instance) {// 操作②:第2次检查
instance = new SingletonDoubleCheck();//操作③
}
}
}
return instance;
}
}
静态内部类的单例模式
类的静态变量被初次访问会触发Java虚拟机对该类进行初始化,即该类的静态变量的值会变为其初始值 而不是默认值。因此,静态方法getInstance()被调用的时候Java虚拟机会初始化这个方法所访问的 内部静态类InstanceHolder。这使得InstanceHolder的静态变量INSTANCE被初始化,从而使 StaticHolderSingleton类的唯一实例得以创建。由于类的静态变量只会创建一次,因此 StaticHolderSingleton (单例类)只会被创建一次。
public class SingletonStaticHolder {
/** 私有构造器 */
private SingletonStaticHolder() {
}
private static class InstanceHolder {
/** 保存外部类的唯一实例 */
final static SingletonStaticHolder INSTANCE = new SingletonStaticHolder();
}
public static SingletonStaticHolder getInstance() {
return InstanceHolder.INSTANCE;
}
}
枚举实现单例模式
public class SingletonEnumBase {
public static void main(String[] args) {
Singleton.INSTANCE.doSomething();
}
public enum Singleton {
INSTANCE;
Singleton() {
}
public void doSomething() {
}
}
}
这里,枚举类型Singleton 相当于-一个单例类,其字段INSTANCE值相当于该类的唯一实例。这个实例 是在Singleton.INSTANCE 初次被引用的时候才被初始化的。仅访问Singleton本身(比如上述的 Singleton.class. getName()调用)并不会导致Singleton的唯一实例被初始化。
参考资料
文档信息
- 本文作者:Bob.Zhu
- 本文链接:https://adolphor.github.io/2021/08/06/01-singleton/
- 版权声明:自由转载-非商用-非衍生-保持署名(创意共享3.0许可证)