一、单例模式

2022-09-21 09:57:23 浏览数 (1)

解决的问题

  • 有些数据在系统中只应该保存一份,比如系统的配置信息类
  • 资源访问冲突的问题,比如多个logger写入同一个日志文件

几种实现方式

饿汉式

  • 静态成员变量,类加载时实例化
  • 线程安全
  • 不支持延迟加载
代码语言:javascript复制
public class HungryManDemo {

    private static final HungryManDemo instance = new HungryManDemo();

    private HungryManDemo() {}

    public static HungryManDemo getInstance(){
        return instance;
    }
}

懒汉式

  • 支持延迟加载
  • 不支持高并发
代码语言:javascript复制
public class LazyManDemo {

    private static LazyManDemo instance;

    private LazyManDemo() {}

    public static LazyManDemo getInstance() {
        if (instance == null) {
            instance = new LazyManDemo();
        }
        return instance;
    }
}

懒汉式——双重检查

  • 支持高并发
  • 支持延迟加载
  • 使用volatile,禁止指令重排序
  • 在双重检测时可使用局部变量优化,减少访问volatile修饰的变量,以提升性能
  • 实现复杂
代码语言:javascript复制
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()也并不需要加同步。
代码语言:javascript复制
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》作者推荐使用的方法。
代码语言:javascript复制
public enum EnumDemo {
    INSTANCE;
}

序列化问题

反序列化会产生新对象,违反单例规则

解决方案:JVM从内存中反序列化地"组装"一个新对象时,会自动调用类的readResolve方法,我们可以通过此方法返回指定好的对象。

代码语言:javascript复制
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;
    }
}

反射问题

反射获取构造器,进行实例化产生新对象

解决方案:第二次实例化的时候,抛出异常

代码语言:javascript复制
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;
    }
}

0 人点赞