单例设计模式


定义

保证一个类只有一个实例,并且提供一个全局访问点

懒汉式

1)线程安全
2)加锁优化=> double check
3)编译器(JIT),CPU 有可能对指令进行重排序,导致使用到尚未初始化的实例。

  • 对象创建的步骤: 分配空间 => 初始化 => 引用赋值 在编译时 第2、3步是可以互换的
  • 当步骤互换,先进行引用赋值,当另一个需要获取时,由于还未进行初始化,所以会产生空指针异常
  • 所以需要 通过添加 volatile关键字进行修饰,防止指令重排。

优化过程

  • 基本
public class LazySingleton {
    private static LazySingleton instance;

    //创建私有构造函数,防止使用 new 创建
    private LazySingleton(){}

    public static LazySingleton getInstance(){
        if(instance == null){
            try {
                Thread.sleep(20);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            instance = new LazySingleton();
        }
        return instance;
    }
}
  • 问题

当两个线程几乎同时进入 if ,就会创建两个不同的对象。

  • 解决:添加程序🔒

    public synchronized static LazySingleton getInstance(){
          if(instance == null){
              try {
                  Thread.sleep(20);
              } catch (InterruptedException e) {
                  e.printStackTrace();
              }
              instance = new LazySingleton();
          }
          return instance;
      }
    
  • 问题 : 当创建实例对象后,不断获取会降低获取效率(不断加锁)。

  • 解决 : 只需要在 if 内部添加程序🔒,以提高程序的效率
public static LazySingleton getInstance(){
        if(instance == null){
            synchronized (LazySingleton.class){
                if (instance == null){
                    instance = new LazySingleton();
                }
            }
        }
        return instance;
    }

最终代码

class LazySingletion{
    private volatile static LazySingletion instance;
    private static LazySingletion(){

    }

    public static LazySingletion getInstance(){
        if(instance == null){
            synchronized (LazySingletion.class){
                if (instance == null) {
                    instance = new LazySingletion();
                }
            }
        }
        return instance;
    }

}

饿汉式

JVM 保证线程安全的,程序在编译时就创建了对象,所以不存在线程安全问题。

class HangrySingleton{

    private static HangrySingleton instance = new HangrySingleton();
    private HangrySingleton(){}

    public static HangrySingleton getInstance(){
        return instance;
    }

}

静态内部类

懒加载的方式: 将单例对象 放置在内部类中,当调用 getInstance 方法时,需要内部类的属性,这时会创建 实例对象,是懒加载的方式。

class InnerClassSingleton{
    private static class InnerClass{
        private static InnerClassSingleton instance = new InnerClassSingleton();
    }

    private InnerClassSingleton(){
        // 防止使用反射机制进行创建
        // 懒汉模式不能进行防护
        if(Inner.instance != null){
            throw new RuntimeException("已经创建过实例!~");
        }
    }

    public static InnerClassSingleton getInstance(){
        return InnerClass.instance;
    }
}

Enum 单例模式

反射安全、线程安全

反射攻击

使用反射机制,可以调用私有构造函数,可能造成打破单例的规则。

private InnerClassSingleton(){
    // 防止使用反射机制进行创建
    // 懒汉模式不能进行防护
    if(Inner.instance != null){
        throw new RuntimeException("已经创建过实例!~");
    }
}

序列化的单例模式

将对象存入硬盘,读取的对象和调用方法获取的对象不一致。

  • 解决:
  1. 对象中继承序列化接口 Serializable
  2. 添加方法 : Object readResolve() throws ObjectStreamException;
  3. 添加版本🆔 : static final long serialVersionUID = 43L;
public class InnerSingleton implements Serializable {
    static final long serialVersionUID = 43L;

    private static class Inner{
        private static InnerSingleton instance = new InnerSingleton();
    }

    private InnerSingleton(){
        if(Inner.instance != null){
            throw new RuntimeException("已经创建过实例!~");
        }
    }

    public static InnerSingleton getInstance(){
        return Inner.instance;
    }

    Object readResolve() throws ObjectStreamException {
        return Inner.instance;
    }

}
  • 测试
    @Test
    public void save() throws IOException {
        InnerSingleton instance = InnerSingleton.getInstance();
        ObjectOutputStream out = new ObjectOutputStream(new FileOutputStream("InnerSingleton"));
        out.writeObject(instance);
        out.close();
    }

    @Test
    public void read() throws IOException, ClassNotFoundException {
        ObjectInputStream in = new ObjectInputStream(new FileInputStream("InnerSingleton"));
        InnerSingleton innerSingleton = (InnerSingleton) in.readObject();
        in.close();
        Assert.assertEquals(true, InnerSingleton.getInstance() == innerSingleton);
        //通过
    }

Enum类型序列化

Enum 类型可直接进行序列化,不需要进行以上操作。

其他示例

  • Spring & JDK
    • java.lang.Runtime => 饿汉式
    • java.util.Currency => double check
    • org.springframework.aop.framework.ProxyFactoryBean
    • org.springframework.beans.factory.support.DefaultSingletonBeanRegistry
  • Tomcat
    • org.apache.catalina.webresources.TomcatURLStreamHandlerFactory

参考内容

程序员必备的13种设计模式你真的掌握了吗?全套教学视频让你彻底弄懂


文章作者: KawYang
版权声明: 本博客所有文章除特別声明外,均采用 CC BY 4.0 许可协议。转载请注明来源 KawYang !
评论
  目录