本文最后更新于 1326 天前,其中的信息可能已经有所发展或是发生改变。
解决的问题
- 有些数据在系统中只应该保存一份,比如系统的配置信息类
- 资源访问冲突的问题,比如多个logger写入同一个日志文件
几种实现方式
饿汉式
- 静态成员变量,类加载时实例化
-
线程安全
-
不支持延迟加载
public class HungryManDemo {
private static final HungryManDemo instance = new HungryManDemo();
private HungryManDemo() {}
public static HungryManDemo getInstance(){
return instance;
}
}
懒汉式
-
支持延迟加载
-
不支持高并发
public class LazyManDemo {
private static LazyManDemo instance;
private LazyManDemo() {}
public static LazyManDemo getInstance() {
if (instance == null) {
instance = new LazyManDemo();
}
return instance;
}
}
懒汉式——双重检查
-
支持高并发
-
支持延迟加载
-
使用
volatile
,禁止指令重排序 -
在双重检测时可使用局部变量优化,减少访问
volatile
修饰的变量,以提升性能 -
实现复杂
public class LazyManDoubleCheckDemo {
private static volatile LazyManDoubleCheckDemo instance;
private LazyManDoubleCheckDemo() {}
public static LazyManDoubleCheckDemo getInstance() {
LazyManDoubleCheckDemo temp = instance;
if (temp == null) {
synchronized (LazyManDoubleCheckDemo.class) {
temp = instance;
if (temp == null) {
temp = new LazyManDoubleCheckDemo();
instance = temp;
}
}
}
return instance;
}
}
静态内部类
-
支持延迟加载
-
支持高并发
-
实现比双重检测简单
-
SingletonClass没有static的属性,因此并不会被初始化。直到调用getInstance()的时候,会首先加载SingletonClassInstance类,这个类有一个static的SingletonClass实例,因此需要调用SingletonClass的构造方法,然后getInstance()将把这个内部类的instance返回给使用者。由于这个instance是static的,因此并不会构造多次。
由于SingletonClassInstance是私有静态内部类,所以不会被其他类知道,同样,static语义也要求不会有多个实例存在。并且,JSL规范定义,类的构造必须是原子性的,非并发的,因此不需要加同步块。同样,由于这个构造是并发的,所以getInstance()也并不需要加同步。
public class StaticInnerClassDemo {
private StaticInnerClassDemo() {}
private static class SingletonHolder{
private static final StaticInnerClassDemo instance = new StaticInnerClassDemo();
}
public static StaticInnerClassDemo getInstance() {
return SingletonHolder.instance;
}
}
枚举
- 不支持延迟加载
-
支持高并发
-
实现最简单
-
使用枚举除了线程安全和防止反射调用构造器之外,还提供了自动序列化机制,防止反序列化的时候创建新的对象。因此,《Effective Java》作者推荐使用的方法。
public enum EnumDemo {
INSTANCE;
}
序列化问题
反序列化会产生新对象,违反单例规则
解决方案:JVM从内存中反序列化地"组装"一个新对象时,会自动调用类的readResolve方法,我们可以通过此方法返回指定好的对象。
public class SerialProblem {
private static volatile SerialProblem instance;
private SerialProblem() {}
public static SerialProblem getInstance() {
SerialProblem temp = instance;
if (temp == null) {
synchronized (SerialProblem.class) {
temp = instance;
if (temp == null) {
temp = new SerialProblem();
instance = temp;
}
}
}
return instance;
}
private Object readResolve() {
return instance;
}
}
反射问题
反射获取构造器,进行实例化产生新对象
解决方案:第二次实例化的时候,抛出异常
public class ReflectProblem {
private static volatile ReflectProblem instance;
private ReflectProblem() {
Assert.isNull(instance);
}
public static ReflectProblem getInstance() {
ReflectProblem temp = instance;
if (temp == null) {
synchronized (ReflectProblem.class) {
temp = instance;
if (temp == null) {
temp = new ReflectProblem();
instance = temp;
}
}
}
return instance;
}
}
JDK源码里JRELocaleProviderAdapter维护的LocaleProvider全是单例,使用的是DCL,volatile
![image-20210903144157438]()
查看图片