zoukankan      html  css  js  c++  java
  • Java设计模式001 --- 单例模式

    前言

    什么是单例模式?就是在一个应用程序中,一个类的实例有且仅有一个;这个类负责创建该类的实例;

    一般来说单例是有状态的对象,比如全局设置、数据库dao实例、全局资源等,并且可以根据需求延迟加载或者即时加载;

    即时加载单例模式

    1、静态公有域单例(我不习惯别人说的饿汉、懒汉)

    public class Singleton1 {
        public static final Singleton1 INSTANCE = new Singleton1();
    
        // 私有构造器, 防止被实例化
        private Singleton1() {
        }
    public void doWhatever() {
        }
    }

    使用方法:SingleTon1.INSTANCE

    特点:在类加载的时候就初始化好了,无线程安全问题;

               即时加载,但是存在单例被破坏的风险,如使用反射、序列化

    反射方式:

    public static void main(String[] args) throws NoSuchMethodException, IllegalAccessException, InvocationTargetException, InstantiationException {
        Class<Singleton1> clazz = Singleton1.class;
        Constructor<Singleton1> constructor = clazz.getDeclaredConstructor();
        constructor.setAccessible(true);
        Singleton1 singleton1 = constructor.newInstance();
        System.out.println(singleton1 == Singleton1.getInstance());
    }

    序列化方式(前提是单例类实现了Serializale):

    public static void main(String[] args) {
        Singleton1 singleton = Singleton1.getInstance();
        File file = new File("src\main\resources\singleton.txt");
        try (FileOutputStream fos = new FileOutputStream(file);
             ObjectOutputStream oos = new ObjectOutputStream(fos)) {
            oos.writeObject(singleton);
        } catch (IOException ex) {
            ex.printStackTrace();
        }
    
        try (FileInputStream fis = new FileInputStream(file);
             ObjectInputStream ois = new ObjectInputStream(fis)) {
            Singleton1 singleton1 = (Singleton1) ois.readObject();
            System.out.println(singleton == singleton1);
        } catch (IOException | ClassNotFoundException e) {
            e.printStackTrace();
        }
    }

     2、上述问题的解决方案

    为了解决反射对单例造成的破坏,可以做如下修改:在私有构造方法中判断实例是否为null,否则抛运行时异常

    // 私有构造器, 防止被实例化
    private Singleton1() {
        if (instance != null) {
            throw new RuntimeException();
        }
    }

    上述方法对序列化不起作用,这也从侧面验证了反序列化创建的对象不依赖类的构造器,而是由JVM创建的;

    为了解决序列化对单例造成的破坏,可做如下修改:在序列化类中添加私有readResolve方法

    private Object readResolve() {
        return instance;
    }

    3、静态工厂方法单例

    public class SingletonFactory implements Serializable {
        private static final SingletonFactory INSTANCE = new SingletonFactory();
    
        private SingletonFactory() {
            if (INSTANCE != null) {
                throw new RuntimeException();
            }
        }
    
        public static SingletonFactory getInstance() {
            return INSTANCE;
        }
    
        private Object readResolve() {
            return INSTANCE;
        }
    
        public static void main(String[] args) {
            SingletonFactory instance1 = SingletonFactory.getInstance();
            // 序列化
            File file = new File("src\main\resources\serial\serial_singleton.txt");
            try (FileOutputStream fos = new FileOutputStream(file);
                 ObjectOutputStream oos = new ObjectOutputStream(fos)) {
                oos.writeObject(instance1);
            } catch (IOException ex) {
                ex.printStackTrace();
            }
            // 反序列化
            try (FileInputStream fis = new FileInputStream(file);
                 ObjectInputStream ois = new ObjectInputStream(fis)) {
                SingletonFactory instance2 = (SingletonFactory) ois.readObject();
                System.out.println(instance1 == instance2);
            } catch (IOException | ClassNotFoundException ex) {
                ex.printStackTrace();
            }
    
            // 反射
            try {
                Constructor<SingletonFactory> constructor = SingletonFactory.class.getDeclaredConstructor();
                SingletonFactory instance3 = constructor.newInstance();
                System.out.println(instance1 == instance3);
            } catch (NoSuchMethodException | InstantiationException | IllegalAccessException | InvocationTargetException e) {
                e.printStackTrace();
            }
        }
    }

    和静态公有域相比,静态工厂方法更灵活,可以修改单例为延迟加载、甚至可以改成非单例

    4、枚举单例模式

    虽然有以上方法可以解决反射和序列化带来的问题,但是有更简便的单例模式 --- 枚举单例

    枚举单例有如下几个有点:线程安全、能够防止反射和序列化带来的破坏、实现简单

    public enum SingletonEnum {
        INSTANCE;
    
        public void doWhatever() {
            System.out.println("Single Enum.");
        }
    }

    使用方法:SingletonEnum.INSTANCE.doWhatever()

    验证下反射和序列化场景是否会破坏单例:

    public static void main(String[] args) throws NoSuchMethodException, IllegalAccessException, InvocationTargetException, InstantiationException {
        SingletonEnum instance = SingletonEnum.INSTANCE;
        // 验证两次获取的是否是同一个对象
        System.out.println(instance == SingletonEnum.INSTANCE);
        // 序列化场景
        File file = new File("src\main\resources\singleton.txt");
        try (FileOutputStream fos = new FileOutputStream(file);
             ObjectOutputStream oos = new ObjectOutputStream(fos)) {
            oos.writeObject(instance);
        } catch (IOException ex) {
            ex.printStackTrace();
        }
    
        try (FileInputStream fis = new FileInputStream(file);
             ObjectInputStream ois = new ObjectInputStream(fis)) {
            SingletonEnum instance2 = (SingletonEnum) ois.readObject();
            System.out.println(instance == instance2);
        } catch (IOException | ClassNotFoundException e) {
            e.printStackTrace();
        }
    
        // 反射场景
        Class<SingletonEnum> clazz = SingletonEnum.class;
        Constructor<SingletonEnum> constructor = clazz.getDeclaredConstructor(String.class, int.class);
        constructor.setAccessible(true);
        SingletonEnum instance1 = constructor.newInstance();
        System.out.println(instance == instance1);
    }

    运行结果:

     为啥使用反射的时候会抛异常呢?这是因为反射的源码是这样写的:

     

     那么又为啥枚举单例能够保证单例不被序列化破坏呢?

     这是因为Java中规定,每个枚举变量在JVM中都是唯一的,并且Java还规定,枚举类在反序列化时使用枚举类的valueOf方法

     枚举类反编译结果如下:

     

     线程安全又是为什么?从反编译结果就能看出,INSTANCE是静态的,并且初始化时就创建了实例

    延迟加载

    有些场景下单例需要延迟加载,比如:(1)单例加载的资源较多,并且使用率低;(2)依赖其他延迟加载资源,即时加载导致数据不完整;

    1、静态内部类单例

    public class StaticInnerSingleton {
        private static class SingleInner {
            private static final StaticInnerSingleton INSTANCE = new StaticInnerSingleton();
        }
    
        private StaticInnerSingleton() {
        }
    
        public static StaticInnerSingleton getInstance() {
            return SingleInner.INSTANCE;
        }
    }

    2、双重锁模式

    public class SingleLock {
        private static volatile SingleLock instance;
    
        private SingleLock() {
        }
    
        public static SingleLock getInstance() {
            // 降低加锁带来的性能开销
            if (instance != null) {
                synchronized (SingleLock.class) {
                    // 防止被实例化多次
                    if (instance != null) {
                        instance = new SingleLock();
                    }
                }
            }
            return instance;
        }
    }

    这种方法性能较差,建议延迟加载使用静态内部类单例模式

    spring中的单例

    spring中的单例指的是在spring IOC容器中只有一个实例,但是一个java程序中可以有多个spring IOC容器,并不保证java应用程序中的单例

  • 相关阅读:
    Java中的pom.xml
    Java中request请求配置
    java,名称工具类。手机号加星。
    Java数据库查询与循环处理
    php接口分页
    java中sql映射机制
    java中的model映射
    Java启动项目
    httpSession.removeAttribute 移除header中的属性
    mybatisplus 查询数据
  • 原文地址:https://www.cnblogs.com/sniffs/p/12901754.html
Copyright © 2011-2022 走看看