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

    单例模式是最简单的也是设计模式系列书籍开篇第一个讲到的模式,在平时的开发中也经常用它来保证获取的都是同一个实例。

    定义:确保某一个类只有一个实例,而且自行实例化并向整个系统提供这个实例。

    饿汉模式

    public class HungrySingleton {
        private static final HungrySingleton singleton = new HungrySingleton();
    
        //限制外部产生HungrySingleton对象
        private HungrySingleton(){ }
    
        //向外提供获取示例的静态方法
        public static HungrySingleton getInstance() {
            return singleton;
        }
    
        //other methods
    }

    饿汉模式是类加载时候就创建对象,利用了jvm特性保证了线程的安全性。

    • getInstance()方法是static的保证了通过类名可直接获取实例
    • 私有构造方法保证了只有自己才可以创建实例

    懒汉模式

    双重检查加锁 方式

     1 public class LazySingleton {
     2     private static volatile LazySingleton singleton = null;
     3 
     4     private LazySingleton() { }
     5 
     6     public static LazySingleton getSingleton() {
     7         if (singleton == null) { //不用每次获取对象都强制加锁,为了提升了效率
     8             synchronized (LazySingleton.class) {
     9                 if (singleton == null) {
    10                     singleton = new LazySingleton();
    11                 }
    12             }
    13         }
    14         return singleton;
    15     }
    16 }
    • 第7行代码判空是为了提高效率,不用每次获取对象都强制加锁;
    • 第8行同步加锁是为了线程安全;
    • 第9行判空是为了保证单例对象的唯一性,只有没被创建才去创建。
    • volatile关键字为了保证singleton对象的可见性并禁止编译器对其进行编译指令重排序优化。

    静态内部类 模式

    内部类有用static修饰和不用static修饰的内部类:

    • 用static修饰的是静态内部类,相当于其外部类的static成员,不依赖与外部类的实例,静态内部类只可以引用外部类的静态成员属性和静态方法,只在第一次使用到静态内部类的时候才会被装载
    • 不用static修饰的内部类称为对象级别内部类,依赖于外部的类实例
    public class Singleton2 {
        /**
         * 类级的内部类,也就是静态的成员式内部类,该内部类的实例与外部类的实例
         * 没有绑定关系,而且只有被调用到时才会装载,从而实现了延迟加载。
         */
        private static class Singleton2Holder {
            /**
             * 静态初始化器,由JVM来保证线程安全
             */
            private static Singleton2 singleton = new Singleton2();
        }
    
        private Singleton2() {
            //System.out.println("singleton2 private construct method called");
        }
    
        public static Singleton2 getSingleton() {
            //System.out.println("singleton2 getSingleton method called");
            return Singleton2Holder.singleton;
        }
    
        private String name;
    
        public void desc() {
            //System.out.println("singleton2 desc method called");
        }
    }

    是饿汉模式的变种形式,利用jvm加载保证线程安全,并且实现了懒加载,只在获取实例的时候才去创建。

    JDK中单例的应用

    Runtime单例实现

    使用的是饿汉模式

    破坏单例模式

    反射破坏单例模式

    /**饿汉模式——反射创建对象*/
    Class<HungrySingleton> singletonClass = HungrySingleton.class;
    Constructor<HungrySingleton> singletonConstructor = singletonClass.getDeclaredConstructor();
    singletonConstructor.setAccessible(true);
    /**先反射创建*/
    HungrySingleton hungrySingleton = singletonConstructor.newInstance();
    /**再通过单例模式获取实例*/
    HungrySingleton instance = HungrySingleton.getInstance();
    
    System.out.println(hungrySingleton);
    System.out.println(instance);

    通过反射修改构造函数可以被访问,通过反射构造的结果和单例模式获取的不是同一个对象。

    序列化破坏单例模式

    HungrySingleton instance = HungrySingleton.getInstance();
    jsonString = JSON.toJSONString(instance);
    HungrySingleton singleton = JSON.parseObject(jsonString, HungrySingleton.class);
    System.out.println(instance == singleton);

    防止被破坏

    防止反射创建对象

    反射通过调用构造函数来创建对象,如果在构造函数里抛出异常,就可以组织反射创建对象(这种方式不适用与懒汉模式)。

    public class HungrySingleton {
        private static final HungrySingleton singleton = new HungrySingleton();
    
        //限制外部产生Singleton对象
        private HungrySingleton() {
            if (singleton != null) {
                throw new RuntimeException("不允许创建对象!");
            }
            System.out.println("singleton private construct method called");
        }
    
        //向外提供获取示例的静态方法
        public static HungrySingleton getInstance() {
            System.out.println("create singleton instance");
            return singleton;
        }
    }

    再用反射创建对象会报错

    /**饿汉模式——反射创建对象*/
    Class<HungrySingleton> singletonClass = HungrySingleton.class;
    Constructor<HungrySingleton> singletonConstructor = singletonClass.getDeclaredConstructor();
    singletonConstructor.setAccessible(true);
    /**先反射创建*/
    HungrySingleton hungrySingleton = singletonConstructor.newInstance();
    /**再通过单例模式获取实例*/
    HungrySingleton instance = HungrySingleton.getInstance();
    
    System.out.println(hungrySingleton);
    System.out.println(instance);

    懒汉模式仍然会被破坏(当反射先于懒汉模式创建对象时,仍然会创建多个对象)

    /**懒汉模式——反射创建对象*/
    Class<LazySingleton> lazySingletonClass = LazySingleton.class;
    Constructor<LazySingleton> lazySingletonConstructor = lazySingletonClass.getDeclaredConstructor();
    lazySingletonConstructor.setAccessible(true);
    /**先通过反射获取实例*/
    LazySingleton lazySingleton = lazySingletonConstructor.newInstance();
    /**再通过单例模式获取实例*/
    LazySingleton lazyInstance = LazySingleton.getInstance();
    
    System.out.println(lazySingleton);
    System.out.println(lazyInstance);

    懒汉模式仍然会创建两个对象:

    singleton.LazySingleton@593634ad 

    singleton.LazySingleton@20fa23c1

    防止反序列化创建对象

    如果是使用ObjectInputStream方式序列化,可以使用readResolve方法来控制。但序列化的方法有很多种,这种方式并不可靠。

    枚举单例

    《effective java》第77条:对于实例控制,枚举类型优于readResolve。

    以下是一个枚举单例的示例

    public enum EnumSingleton {
        INSTANCE;
    
        public String getDesc() {
            return "desc";
        }
    
        public static void process() {
            System.out.println("static process method");
        }
    
        private String name;
        private Integer age;
    
        public String getName() {
            return name;
        }
        public void setName(String name) {
            this.name = name;
        }
    
        public Integer getAge() {
            return age;
        }
        public void setAge(Integer age) {
            this.age = age;
        }
    }

    使用时候直接获取 EnumSingleton.INSTANCE 即可获取当前实例,而且序列化与反序列化不会创建对象。

    /**枚举获取实例*/
    EnumSingleton instance = EnumSingleton.INSTANCE;
    instance.setName("this is a enum singleton");
    instance.setAge(28);
    System.out.println("instance.name:"+instance.getName()+", instance.age:"+instance.getAge());
    
    /**序列化*/
    String jsonString = JSON.toJSONString(instance);
    /**反序列化创建对象*/
    EnumSingleton serializerInstance = JSON.parseObject(jsonString, EnumSingleton.class);
    System.out.println(instance == serializerInstance);
    
    Class<EnumSingleton> enumSingletonClass = EnumSingleton.class;
    Constructor<EnumSingleton> enumSingletonConstructor = enumSingletonClass.getDeclaredConstructor();
    enumSingletonConstructor.setAccessible(true);
    /**反射创建*/
    EnumSingleton enumSingleton = enumSingletonConstructor.newInstance();
    System.out.println(enumSingleton);

    输出:

     instance.name:this is a enum singleton, instance.age:28

    true 

    java.lang.NoSuchMethodException: singleton.EnumSingleton.<init>()

      at java.lang.Class.getConstructor0(Class.java:3082)

    总结,实现单例模式的唯一推荐方法,使用枚举类来实现。

    guava中的单例

     guava里有个Suppliers提供了memoize方法,用法如下:

    Suppliers.memoize(new Supplier<Object>() {
                @Override
                public Object get() {
                    return new Demo();
                }
            });

    查看其实现源码,将传入的Suppliers作为代理传给MemoizingSupperlizer,返回一个类型为MemoizingSupperlizer类型的Supperlier对象;如果不是MemoizingSupperlizer类型,创建一个MemoizingSupperlizer实例返回:

    MemoizingSupperlizer内部get方法使用double-check方式实现了只执行一次创建对象方法

    同样的还有 ExpiringMemoizingSupplier 方法,支持过期时间只有再次调用get方法创建对象(可以用来实现缓存)

    参考

    代码示例

    《设计模式之禅》
    https://www.cnblogs.com/shangxinfeng/p/6754345.html
    https://www.cnblogs.com/ttylinux/p/6498822.html?utm_source=itdadao&utm_medium=referral
    https://blog.csdn.net/hintcnuie/article/details/17968261
    https://www.cnblogs.com/ldq2016/p/6627542.html

  • 相关阅读:
    List接口的实现类
    Java 容器概述
    PayPal支付接口方式(checkout)集成
    Java Bio、Nio、Aio区别
    Java Aio
    Java Nio
    Java Bio
    Java Io流
    Java 23种设计模式
    SQL检测超时工具类
  • 原文地址:https://www.cnblogs.com/mr-yang-localhost/p/9644417.html
Copyright © 2011-2022 走看看