单例模式
基本介绍
单例模式(Singleton Pattern)是 Java 中最简单的设计模式之一,提供了一种创建对象的最佳方式
在一个程序当中 一个类只创建一个对象 就是单例模式
单例设计模式分类两种:
- 饿汉式:类加载就会导致该单实例对象被创建 ---》 悲观 锁
有求必应 添狗 - 懒汉式:类加载不会导致该单实例对象被创建,而是首次使用该对象时才会创建 --》 乐观锁
我找了个对象 我无所谓
饿汉式
饿汉式在类加载的过程导致该单实例对象被创建,虚拟机会保证类加载的线程安全,但是如果只是为了加载该类不需要实例,则会造成内存的浪费
- 静态变量的方式:
public final class Singleton { // 私有构造方法 private Singleton() {} // 在成员位置创建该类的对象 private static final Singleton instance = new Singleton(); // 对外提供静态方法获取该对象 public static Singleton getInstance() { return instance; } 1次来创建了一个对象 2次来把第一次的对象销毁 在创建一个对象 3 接着销毁 接着创建 // 解决序列化问题 protected Object readResolve() { return INSTANCE; } }
- 加 final 修饰,所以不会被子类继承,防止子类中不适当的行为覆盖父类的方法,破坏了单例
- 防止反序列化破坏单例的方式:
- 对单例声明 transient,然后实现 readObject(ObjectInputStream in) 方法,复用原来的单例
条件:访问权限为 private/protected、返回值必须是 Object、异常可以不抛 - 实现 readResolve() 方法,当 JVM 从内存中反序列化地组装一个新对象,就会自动调用 readResolve 方法返回原来单例
- 构造方法设置为私有,防止其他类无限创建对象,但是不能防止反射破坏
- 静态变量初始化在类加载时完成,由 JVM 保证线程安全,能保证单例对象创建时的安全
- 提供静态方法而不是直接将 INSTANCE 设置为 public,体现了更好的封装性、提供泛型支持、可以改进成懒汉单例设计
- 静态代码块的方式:
public class Singleton { // 私有构造方法 private Singleton() {} // 在成员位置创建该类的对象 private static Singleton instance; static { instance = new Singleton(); } // 对外提供静态方法获取该对象 public static Singleton getInstance() { return instance; } }
枚举方式:枚举类型是所用单例实现中唯一一种不会被破坏的单例实现模式
public enum Singleton { INSTANCE; public void doSomething() { System.out.println("doSomething"); } } public static void main(String[] args) { Singleton.INSTANCE.doSomething(); }
- 问题1:枚举单例是如何限制实例个数的?每个枚举项都是一个实例,是一个静态成员变量
- 问题2:枚举单例在创建时是否有并发问题?否
- 问题3:枚举单例能否被反射破坏单例?否,反射创建对象时判断是枚举类型就直接抛出异常
- 问题4:枚举单例能否被反序列化破坏单例?否
- 问题5:枚举单例属于懒汉式还是饿汉式?饿汉式
- 问题6:枚举单例如果希望加入一些单例创建时的初始化逻辑该如何做?添加构造方法
反编译结果:
public final class Singleton extends java.lang.Enum<Singleton> { // Enum实现序列化接口 public static final Singleton INSTANCE = new Singleton(); }
懒汉式 乐观
- 线程不安全
public class Singleton { // 私有构造方法 private Singleton() {} // 在成员位置创建该类的对象 private static Singleton instance; 1 来 创建一个对象 2 判断你现在是不是有对象 3 直接返回你原本的对象 // 对外提供静态方法获取该对象 public static Singleton getInstance() { if(instance == null) { // 多线程环境,会出现线程安全问题,可能多个线程同时进入这里 instance = new Singleton(); } return instance; } }
双端检锁机制
在多线程的情况下,可能会出现空指针问题,出现问题的原因是 JVM 在实例化对象的时候会进行优化和指令重排序操作,所以需要使用 volatile
关键字
public class Singleton { // 私有构造方法 private Singleton() {} private static volatile Singleton instance; // 对外提供静态方法获取该对象 public static Singleton getInstance() { // 第一次判断,如果instance不为null,不进入抢锁阶段,直接返回实例 if(instance == null) { synchronized (Singleton.class) { // 抢到锁之后再次判断是否为null if(instance == null) { instance = new Singleton(); } } } return instance; } }
静态内部类方式
public class Singleton { // 私有构造方法 private Singleton() {} private static class SingletonHolder { private static final Singleton INSTANCE = new Singleton(); } // 对外提供静态方法获取该对象 public static Singleton getInstance() { return SingletonHolder.INSTANCE; } }
- 内部类属于懒汉式,类加载本身就是懒惰的,首次调用时加载,然后对单例进行初始化
类加载的时候方法不会被调用,所以不会触发 getInstance 方法调用 invokestatic 指令对内部类进行加载;加载的时候字节码常量池会被加入类的运行时常量池,解析工作是将常量池中的符号引用解析成直接引用,但是解析过程不一定非得在类加载时完成,可以延迟到运行时进行,所以静态内部类实现单例会延迟加载 - 没有线程安全问题,静态变量初始化在类加载时完成,由 JVM 保证线程安全