zoukankan      html  css  js  c++  java
  • 单例模式


    提供了一种创建对象的最佳模式

    这种模式涉及到一个单一的类,该类负责创建自己的对象,同时确保只有单个对象被创建。这个类提供了一种访问其唯一的对象的方式,可以直接访问,不需要实例化该类的对象

    应用:任务管理器、JDK 中 Runtime 类

    1.1.1 单例模式的结构

    • 单例类:只能创建一个实例的类
    • 访问类:使用单例类

    1.12 单例模式的实现

    分为两种:

    • 饿汉式:类加载就会导致该单例对象被创建
    • 懒汉式:类加载不会导致该单例对象被创建,而是首次使用该对象时才会创建
    1. 饿汉式-方式 1 (静态变量成员变量)

      public class Singleton {
      
          // 1. 创建私有构造方法,外界无法直接方法
          private Singleton() {
          }
      
          // 2. 在本类中创建本类对象
          private static Singleton instance = new Singleton();
      
          // 3. 提供一个公共的访问方式,让外界获取对象
          public static Singleton getInstance() {
              return instance;
          }
      
      }
      
    2. 饿汉式-方式 2(静态代码块)

      public class Singleton {
      
          // 1. 创建静态构造方法
          private Singleton() {
          }
      
          // 2. 声明私有静态 Singleton 类型的变量
          private static Singleton instance;  // null
      
          static {
              instance = new Singleton();
          }
      
          //  对外提供公有方法获取对象
          public static Singleton getInstance() {
              return instance;
          }
      
      }
      

      说明: 随着类加载就被创建,有可能一直未被使用,会存在内存浪费的问题

    3. 枚举方式 -- 方式3

      // 枚举方式创建单例模式
      public enum Singleton {
          INSTANCE;
      }
      
    4. 懒汉式 -- 方式1 -- 线程不安全

      public class Singleton {
          // 1. 创建私有构造器
          private Singleton() {
          }
          // 2. 声明一个对象成员变量
          private static Singleton instance;
      
          // 3. 提供公有访问对象的方法
          public static Singleton getInstance() {
              if (instance == null) {
                  // 多线程访问的话,会造成线程不安全的问题
                  instance = new Singleton();
              }
      //        instance = new Singleton(); // 不加以判断,每次获取到的对象不同
              return instance;
          }
      
    5. 懒汉式 -- 方式2 (添加 synchronized 关键字)

      public class Singleton {
          // 1. 创建私有构造器
          private Singleton() {
          }
          // 2. 声明一个对象成员变量
          private static Singleton instance;
      
          // 3. 提供公有访问对象的方法
          public static synchronized Singleton getInstance() {
              if (instance == null) {
                  // 多线程访问的话,会造成线程不安全的问题(添加 synchronized 关键字)
                  instance = new Singleton();
              }
              return instance;
          }
      

    说明:执行效果很低,只有在初始化 instance 时才会出现线程安全问题,一旦初始化完成就不存在了

    1. 懒汉式 -- 方式3 (双重检查锁)

      没必要让每个线程必须持有锁才能调用该方法,可跳整加锁的时机

      // 双重检查锁机制
      public class Singleton {
      
          private Singleton() {
          }
      
          private  static Singleton instance;
          
          public static Singleton getInstance() {
              // 第一次判断,如果 instance 不为 null,不进入抢锁阶段,直接返回实例
              if (instance == null) {
                  synchronized (Singleton.class) {
                      // 抢到锁之后再次判断是否为 null
                      if (instance == null) {
                          instance = new Singleton();
                      }
                  }
              }
              
              return instance;
          }
      }
      

      说明:在多线程的情况下,可能会出现空指针问题,出现问题的原因是 JVM 在实例化对象的时候会进行优化和指令重排序操作

      解决方法:使用 volatile 关键字,保证可见性和有序性

    2. 懒汉式 --方式4 -- 优化

      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;
          }
      }
      

      说明:添加 volatile 关键字,能够保证在多线程的情况下线程安全也不会有性能问题

    3. 懒汉式 -- 方式5 -- 静态内部类

      public class Singleton {
          private Singleton() {
          }
      
          // 定义一个静态内部类
          private static class SingletonHolder {
              private static final Singleton INSTANCE = new Singleton();
          }
          
          // 对外提供静态方法获取该对象
          public static Singleton getInstance() {
              return SingletonHolder.INSTANCE;
          }
      }
      

      说明:第一次去加载 Singleton 时不会去初始化 INSTANCE, 只有第一次调用 getInstance,虚拟机加载 SingletonHolder 并初始化 INSTANCE,这样不仅能保证线程安全,也能保证 Singleton 类的一致性

      小结:

      静态内部类单例模式时一种优秀的单例模式,是开源项目种比较常用的一种单例模式,在没有加锁的情况下,保证了多线程的安全,并且没有任何性能影响和空间的浪费

    1.14 存在的问题

    破环单例模式:

    使上面定义的单例类(Singleton)可以创建多个对象,枚举方式除外。有两种方式,分别是序列化和反射

    • 序列化反序列化
    // 破坏单例模式,序列化反序列化
    public class Client {
        public static void main(String[] args) throws Exception {
            writeObjectFile();
            // 返回的两个对象不一致
            readObjectFromFile(); 
            readObjectFromFile();
        }
    
        public static void readObjectFromFile() throws IOException, ClassNotFoundException {
            ObjectInputStream ois = new ObjectInputStream(new FileInputStream("../\a.txt"));
            Singleton instance = (Singleton) ois.readObject();
            System.out.println(instance);
            ois.close();
    
        }
    
        public static void writeObjectFile() throws Exception {
            Singleton instance = Singleton.getInstance();
            ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("../\a.txt"));
            oos.writeObject(instance);
            oos.close();
        }
    }
    
    • 反射
    // 反射破环单例模式
    public class ClientTest {
        public static void main(String[] args) throws Exception {
            // 1. 获取 Singleton 的字节码对象
            Class<Singleton> singletonClass = Singleton.class;
            // 2. 获取无参构造方法对象
            Constructor<Singleton> cons = singletonClass.getDeclaredConstructor();
            // 3. 取消访问修饰检查
            cons.setAccessible(true);
            // 4. 创建 Singleton 对象
            Singleton instance = cons.newInstance();
            Singleton instance1 = cons.newInstance();
            System.out.println(instance == instance1); // false
    
        }
    }
    

    1.15 解决方法

    • 序列化、反序列化解决方法

      在 Singleton 类里面添加 readResolve() 方法,在反序列化时被调用,如果定义了这个方法,就返回这个方法的值,如果没有定义,则返回新 new 出来的对象

    • 反射解决方法

      在构造方法中添加判断对象是否为 null,若不是则抛出 RuntimeException 异常

    Now is better than never
  • 相关阅读:
    2019年1月26日训练日记
    2019年1月25日训练日记
    2019年1月24日训练日记
    2019年1月23日训练日记
    2019年1月22日训练日记
    2019年1月21日训练日记
    2019年1月20日训练日记
    2019年1月19日训练日记
    2019年1月18日训练日记
    C语言学习小结
  • 原文地址:https://www.cnblogs.com/alivinfer/p/14645597.html
Copyright © 2011-2022 走看看