单例模式


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

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

应用:任务管理器、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 异常

posted @ 2021-04-11 21:15  -费费  阅读(58)  评论(0编辑  收藏  举报