美文网首页
设计模式之单例模式

设计模式之单例模式

作者: John_0d5b | 来源:发表于2020-01-22 08:51 被阅读0次

    单例模式

    单例模式是确保一个类在任何情况下都只有一个实例, 并自行实例化向整个系统提供一个全局的访问点.

    一、概念

    特征

    1. 只能有一个实例;
    2. 必须自己创建自己的唯一实例;
    3. 必须向整理系统提供这个实例;

    单例模式分类

    • 饿汉式单例;
    • 懒汉式单例;
    • 注册式单例;
    • ThreadLocal 线程式单例;
    • 枚举单例;

    实现步骤

    1. 私有构造方法, 防止多个实例的产生;
    2. 私有静态实例变量, 保证不被外面置空和唯一;
    3. 公共的静态访问方法, 提供自己创建好的实例;

    优点

    1. 某些类创建比较频繁, 对于一些大型对象的创建是一笔很大的系统开销.
    2. 省去了 new 操作符, 降低了系统内存的使用频率, 减轻 GC 压力;
    3. 可以保证内存中只有一个实例, 减少了内存开销, 可以避免对资源的多重占用

    类图

    在这里插入图片描述

    二、代码演示

    饿汉式

    • 特点 : 在类加载的时候就立即初始化,并且创建单例对象. 绝对线程安全,在线程还没出现以前实例化了, 不可能存在访问安全问题, 并且没有加锁, 它的执行效率比较高. 但因为类加载的时候就初始化了, 不管用不用都会占用内存空间, 形成内存空间的浪费.
    /**
     * @Author: CaoJun
     * @Description: 饿汉式单例模式
     * @Create: 2020-01-17 20:09
     **/
    public class HungrySingleton implements Serializable {
    
        private static final long serialVersionUID = -8686933389819315943L;
    
        /**
         * 1. 私有静态实例变量,保证唯一和不被外面置空(饿汉式:一开始就实例化对象)
         */
        private static final HungrySingleton HUNGRY_SINGLETON_INSTANCE = new HungrySingleton();
    
        /**
         * 2. 私有化构造方法, 防止多个实例产生, 判空(防止反射破坏)
         */
        private HungrySingleton() {
            if (HUNGRY_SINGLETON_INSTANCE != null) {
                throw new RuntimeException("不允许创建多个实例");
            }
        }
    
        /**
         * 3. 公有的静态访问方法, 向整个系统提供全局访问点
         */
        public static HungrySingleton getInstance() {
            return HUNGRY_SINGLETON_INSTANCE;
        }
    
        /**
         * 4. 防止序列化: 如果该对象被用于序列化,可以保证对象在序列化前后保持一致
         */
        private Object readResolve() {
            return HUNGRY_SINGLETON_INSTANCE;
        }
    }
    
    • 饿汉式静态代码块
    /**
     * @Author: CaoJun
     * @Description: 饿汉式静态代码块单例模式
     * @Create: 2020-01-17 20:13
     **/
    public class HungryStaticSingleton  implements Serializable {
    
        private static final long serialVersionUID = -4191127512435945546L;
    
        /**
         * 1. 私有静态实例变量,保证唯一和不被外面置空(饿汉式:一开始就实例化对象)
         */
        private static HungryStaticSingleton HUNGRY_STATIC_SINGLETON_INSTANCE = null;
    
        // 2. 静态代码块实例化
        static {
            HUNGRY_STATIC_SINGLETON_INSTANCE = new HungryStaticSingleton();
        }
    
        /**
         * 3. 私有化构造方法, 防止多个实例产生, 判空(防止反射破坏)
         */
        private HungryStaticSingleton() {
            if (HUNGRY_STATIC_SINGLETON_INSTANCE != null) {
                throw new RuntimeException("不允许创建多个实例");
            }
        }
    
        /**
         * 4. 公有的静态访问方法, 向整个系统提供全局访问点
         */
        public HungryStaticSingleton getInstance() {
            return HUNGRY_STATIC_SINGLETON_INSTANCE;
        }
    
         /**
         * 5. 防止序列化破坏: 如果该对象被用于序列化,可以保证对象在序列化前后保持一致
         */
        private Object readResolve() {
            return HUNGRY_STATIC_SINGLETON_INSTANCE;
        }
    }
    

    懒汉式

    • 特点: 被外部类调用的时候才会加载创建实例, 但是会有线程安全问题, 需要使用 synchronized 加锁, 这样就会有性能上的问题
    /**
     * @Author: CaoJun
     * @Description: 懒汉式单例模式
     * @Create: 2020-01-17 20:16
     **/
    public class LazySingleton implements Serializable {
    
        private static final long serialVersionUID = 8580274347085357039L;
    
        /**
         * 1. 持有私有静态实例,防止被引用,此处赋值为null,目的是实现延迟加载
         */
        private static LazySingleton LAZY_SIMPLE_SINGLETON_INSTANCE = null;
    
        /**
         * 2. 私有化构造方法, 防止多个实例产生, 判空(防止反射破坏)
         */
        private LazySingleton() {
            if (LAZY_SIMPLE_SINGLETON_INSTANCE != null) {
                throw new RuntimeException("不允许创建多个实例");
            }
        }
    
        /**
         * 3. 公有的静态访问方法, 创建实例, 向整个系统提供全局访问点
         */
        public static LazySingleton getInstance() {
            // 提高效率
            if (LAZY_SIMPLE_SINGLETON_INSTANCE == null) {
                // 保证线程安全
                synchronized (LazySingleton.class) {
                    if (LAZY_SIMPLE_SINGLETON_INSTANCE == null) {
                        LAZY_SIMPLE_SINGLETON_INSTANCE = new LazySingleton();
                    }
                }
            }
            return LAZY_SIMPLE_SINGLETON_INSTANCE;
        }
    
        /**
         * 4. 防止序列化: 如果该对象被用于序列化,可以保证对象在序列化前后保持一致
         */
        private Object readResolve() {
            return LAZY_SIMPLE_SINGLETON_INSTANCE;
        }
    }
    
    • 懒汉式单例测试
    /**
     * @Author: CaoJun
     * @Description: 
     * @Create: 2020-01-17 22:04
     **/
    public class ExecutorThread implements Runnable {
    
        public void run() {
            LazySingleton singleton = LazySingleton.getInstance();
            System.out.println(Thread.currentThread().getName() + ":" + singleton);
        }
    }
    
    /**
     * @Author: CaoJun
     * @Description: LazySingleton 测试
     * @Create: 2020-01-17 22:03
     **/
    public class LazySingletonTest {
    
        public static void main(String[] args) {
    
            Thread t1 = new Thread(new ExecutorThread());
            Thread t2 = new Thread(new ExecutorThread());
            t1.start();
            t2.start();
    
            System.out.println("End");
        }
    }
    
    • 懒汉式静态内部类
      • 特点: 兼顾饿汉式的内存浪费,也兼顾 synchronized 性能问题
    /**
     * @Author: CaoJun
     * @Description: 懒汉式内部类单例模式
     * @Create: 2020-01-17 20:21
     **/
    public class LazyInnerClassSingleton implements Serializable {
    
    
        private static final long serialVersionUID = -7388698081929929093L;
    
        /**
         * 1. 私有化构造方法, 防止多个实例产生, 判空(防止反射破坏)
         */
        private LazyInnerClassSingleton() {
            if (LazyHolder.LAZY_INNER_CLASS_SINGLETON != null) {
                throw new RuntimeException("不允许创建多个实例");
            }
        }
    
        /**
         * 2. 向整个系统提供全局访问点
         */
        public static LazyInnerClassSingleton getInstance() {
            return LazyHolder.LAZY_INNER_CLASS_SINGLETON;
        }
    
        /**
         * 3. 此处使用一个内部类来维护单例
         */
        private static class LazyHolder {
            private static LazyInnerClassSingleton LAZY_INNER_CLASS_SINGLETON = new LazyInnerClassSingleton();
        }
    
        /**
         * 4. 防止序列化破坏: 如果该对象被用于序列化,可以保证对象在序列化前后保持一致
         */
        private Object readResolve() {
            return getInstance();
        }
    }
    

    枚举式

    • 特点: 防止反射和序列化破坏
    /**
     * @Author: CaoJun
     * @Description: 注册式枚举单例模式
     * @Create: 2020-01-17 20:30
     **/
    public enum EnumSingleton {
        INSTANCE;
    
        private Object data;
    
        public Object getData() {
            return data;
        }
    
        public void setData(Object data) {
            this.data = data;
        }
    
        public static EnumSingleton getInstance() {
            return INSTANCE;
        }
    }
    

    容器缓存式

    • 特点: 适用于创建实例非常多的情况,便于管理。但是,是非线程安全的
    /**
     * @Author: CaoJun
     * @Description: 容器缓存方式单例模式
     * @Create: 2020-01-17 23:15
     **/
    public class ContainerSingleton implements Serializable {
    
        private static final long serialVersionUID = -7388698631929929093L;
        
        /**
         * 1. 私有化构造方法, 防止多个实例产生, 判空(防止反射破坏)
         */
        private ContainerSingleton() {}
        
        /**
         * 2. 创建 Map 容器
         */
        private static Map<String, Object> ioc = new ConcurrentHashMap<String, Object>();
    
        /**
         * 3. 向整个系统提供全局访问点
         */
        public static Object getBean(String className) {
            synchronized (ioc) {
                if (!ioc.containsKey(className)) {
                    Object obj = null;
                    try {
                        obj = Class.forName(className).newInstance();
                        ioc.put(className, obj);
                    } catch (Exception e) {
                        e.printStackTrace();
                    }
                    return obj;
                } else {
                    return ioc.get(className);
                }
            }
        }
        
        /**
         * 4. 防止序列化破坏: 如果该对象被用于序列化,可以保证对象在序列化前后保持一致
         */
        private Object readResolve() {
            return getInstance();
        }
    }
    

    ThreadLocal 线程

    • 特点: ThreadLocal 是将所有的对象全部都放在 ThreadLocalMap 中, 为每一个线程都提供一个对象, 实际上是以空间换时间来实现线程间的隔离的.
    /**
     * @Author: CaoJun
     * @Description: ThreadLocal 线程单例模式
     * @Create: 2020-01-17 20:32
     **/
    public class ThreadLocalSingleton {
    
        /**
         * 1. 使用 ThreadLocal 线程方式创建私有静态实例变量,保证唯一和不被外面置空
         */
        private static final ThreadLocal<ThreadLocalSingleton> SINGLETON_THREAD_LOCAL = new ThreadLocal<ThreadLocalSingleton>(){
            @Override
            protected ThreadLocalSingleton initialValue() {
                return new ThreadLocalSingleton();
            }
        };
    
        /**
         * 2. 私有化构造方法, 防止多个实例产生, 判空(防止反射破坏)
         */
        private ThreadLocalSingleton() {
            if (SINGLETON_THREAD_LOCAL.get() != null) {
                throw new RuntimeException("不允许创建多个实例");
            }
        }
    
        /**
         * 3. 公有的静态访问方法, 向整个系统提供全局访问点
         */
        public static ThreadLocalSingleton getInstance() {
            return SINGLETON_THREAD_LOCAL.get();
        }
        
         /**
         * 4. 防止序列化破坏: 如果该对象被用于序列化,可以保证对象在序列化前后保持一致
         */
        private Object readResolve() {
            return getInstance();
        }
    }
    

    三、使用 Idea 多线程调试单例模式

    在这里插入图片描述 在这里插入图片描述 在这里插入图片描述 在这里插入图片描述 在这里插入图片描述 在这里插入图片描述 在这里插入图片描述 在这里插入图片描述

    四、为什么加上 readResolve(); 方法可以防止序列化破坏?

    • 当我们将一个单例对象创建好,有时候需要将对象序列化然后写入到磁盘,下次使用时再从磁盘中读取到对象,反序列化转化为内存对象。反序列化后的对象会重新分配内存, 即重新创建。那如果序列化的目标的对象为单例对象,就违背了单例模式的初衷,相当 于破坏了单例. 加上 readResolve(); 方法可以防止序列化.
    • 源码解释
    import java.io.FileInputStream;
    import java.io.FileOutputStream;
    import java.io.ObjectInputStream;
    import java.io.ObjectOutputStream;
    
    /**
     * @author CaoJun
     * @Description: 序列化破坏单例模式
     * @Create: 2020-01-17 22:36
     */
    public class LazySingletonTest {
        public static void main(String[] args) {
            LazySingleton s1 = null;
            LazySingleton s2 = LazySingleton.getInstance();
            try (ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("tmp.obj"));
                 ObjectInputStream ois = new ObjectInputStream(new FileInputStream("tmp.obj"));) {
    
                oos.writeObject(s2);
                oos.flush();
                // 进 入 ObjectInputStream 类的 readObject()方法
                s1 = (LazySingleton) ois.readObject();
    
                System.out.println(s1 + "\r\n" + s2 + "\r\n" + (s1 == s2));
            } catch (Exception e) {
                e.printStackTrace();
            }
        }
    }
    
    • 发现在 readObject 中又调用了我们重写的 readObject0() 方法, 进入 readObject0(); 方法
    /**
     * ObjectInputStream#readObject()
     */
    public final Object readObject()
            throws IOException, ClassNotFoundException
        {
            if (enableOverride) {
                return readObjectOverride();
            }
    
            // if nested read, passHandle contains handle of enclosing object
            int outerHandle = passHandle;
            try {
                // 
                Object obj = readObject0(false);
                handles.markDependency(outerHandle, passHandle);
                ClassNotFoundException ex = handles.lookupException(passHandle);
                if (ex != null) {
                    throw ex;
                }
                if (depth == 0) {
                    vlist.doCallbacks();
                }
                return obj;
            } finally {
                passHandle = outerHandle;
                if (closed && depth == 0) {
                    clear();
                }
            }
        }
    
    • 看到 TC_OBJECTD 中判断,调用了 ObjectInputStream#readOrdinaryObject() 方法
    /**
     * ObjectInputStream#readObject0()
     */
    private Object readObject0(boolean unshared) throws IOException {
        // ......
        case TC_OBJECT:
        return checkResolve(readOrdinaryObject(unshared));
        // ......
    }
    
    • 发现调用了 ObjectStreamClass#isInstantiable() 方法
    /**
     * ObjectStreamClass#readOrdinaryObject()
     */
    private Object readOrdinaryObject(boolean unshared)
        // ... 
        Object obj;
        try {
            obj = desc.isInstantiable() ? desc.newInstance() : null;
        } catch (Exception ex) {
            throw (IOException) new InvalidClassException(
                desc.forClass().getName(),
                "unable to create instance").initCause(ex);
        }
        // ...
        if (obj != null &&
            // 调用了 hasReadResolveMethod() 方法
            handles.lookupException(passHandle) == null &&
            desc.hasReadResolveMethod())
        {
            Object rep = desc.invokeReadResolve(obj);
            if (unshared && rep.getClass().isArray()) {
                rep = cloneArray(rep);
            }
            if (rep != obj) {
                // Filter the replacement object
                if (rep != null) {
                    if (rep.getClass().isArray()) {
                        filterCheck(rep.getClass(), Array.getLength(rep));
                    } else {
                        filterCheck(rep.getClass(), -1);
                    }
                }
                handles.setObject(passHandle, obj = rep);
            }
        }
       return obj;
    }
    
    • 判断一下构造方法是否为空,构造方法不为空就返回 true
    /**
     * ObjectStreamClass#isInstantiable()
     */
    boolean isInstantiable() { 
        requireInitialized(); 
        return (cons != null); 
    }
    
    • 判断构造方法是否存在之后,又调用了 hasReadResolveMethod() 方法, 判断是否为空,不为空就返回 true
    boolean hasReadResolveMethod() { 
        requireInitialized(); 
        return (readResolveMethod != null); 
    }
    
    • 通过全局搜索, 在ObjectStreamClass 中找到 readResolve 的赋值, 通过反射找到一个无参的 readResolve() 方法,并且保存下来
    /**
     * ObjectStreamClass#ObjectStreamClass()
     */
    private ObjectStreamClass(final Class<?> cl) {
        // ...
        readResolveMethod = getInheritableMethod(cl, "readResolve", null, Object.class);
        // ...
    }
    
    • invokeReadResolve() 方法中用反射调用了 readResolveMethod()方法, 通过 JDK 源码分析我们可以看出,虽然增加 readResolve() 方法返回实例,解决了单例被破坏的问题。但是,我们通过分析源码以及调试,我们可以看到实际上实例化了两次,只不过新创建的对象没有被返回而已, 该对象会被 GC 回收.
    /**
     * ObjectStreamClass#invokeReadResolve()
     */
    Object invokeReadResolve(Object obj)
            throws IOException, UnsupportedOperationException
    {
        requireInitialized();
        if (readResolveMethod != null) {
            try {
                // 
                return readResolveMethod.invoke(obj, (Object[]) null);
            } catch (InvocationTargetException ex) {
                Throwable th = ex.getTargetException();
                if (th instanceof ObjectStreamException) {
                    throw (ObjectStreamException) th;
                } else {
                    throwMiscException(th);
                    throw new InternalError(th);  // never reached
                }
            } catch (IllegalAccessException ex) {
                // should not occur, as access checks have been suppressed
                throw new InternalError(ex);
            }
        } else {
            throw new UnsupportedOperationException();
        }
    }
    

    五、为什么枚举单例模式能够防止序列化和反射破坏?

    1. 下载 jad 工具, 解压后配置好环境变量, 就可以使用命令行调用了;
    2. 找到工程所在的 class 目录,复制 EnumSingleton.class 所在的路径
    在这里插入图片描述
    1. 使用命令行, 输入命令 jad 后面输入复制好的路径,在 class 目录下会多一个 EnumSingleton.jad 文件。打开 EnumSingleton.jad 文件, 有如下静态代码块: 枚举式单例在静态代码块中就给 INSTANCE 进行了赋值,是饿汉式单例的实现
    static {
        INSTANCE = new EnumSingleton("INSTANCE", 0); 
        $VALUES = (new EnumSingleton[] { INSTANCE }); 
    }
    
    1. 关于序列化的代码: 在 readObject0() 中调用了 readEnum() 方法,来看 readEnum() 中代码实现
    /**
     * ObjectInputStream#readObject()
     */
    private Object readObject0(boolean unshared) throws IOException { 
        // ... 
        case TC_ENUM: 
            return checkResolve(readEnum(unshared)); ... 
    }
    
    1. 发现枚举类型其实通过类名和 Class对象类找到一个唯一的枚举对象。因此,枚举对象不可能被类加载器加载多次
    /**
     * ObjectInputStream#readEnum()
     */
    private Enum<?> readEnum(boolean unshared) throws IOException {
            if (bin.readByte() != TC_ENUM) {
                throw new InternalError();
            }
    
            ObjectStreamClass desc = readClassDesc(false);
            if (!desc.isEnum()) {
                throw new InvalidClassException("non-enum class: " + desc);
            }
    
            int enumHandle = handles.assign(unshared ? unsharedMarker : null);
            ClassNotFoundException resolveEx = desc.getResolveException();
            if (resolveEx != null) {
                handles.markException(enumHandle, resolveEx);
            }
    
            String name = readString(false);
            Enum<?> result = null;
            Class<?> cl = desc.forClass();
            if (cl != null) {
                try {
                    @SuppressWarnings("unchecked")
                    Enum<?> en = Enum.valueOf((Class)cl, name);
                    result = en;
                } catch (IllegalArgumentException ex) {
                    throw (IOException) new InvalidObjectException(
                        "enum constant " + name + " does not exist in " +
                        cl).initCause(ex);
                }
                if (!unshared) {
                    handles.setObject(enumHandle, result);
                }
            }
    
            handles.finish(enumHandle);
            passHandle = enumHandle;
            return result;
        }
    
    1. 运行枚举类型的单例测试, 发现报出 java.lang.NoSuchMethodException 异常, 打开 java.lang.Enum 的源码代码,查看它的构造方法,只有一个 protected 的构造方法
    /**
     * java.lang.Enum
     */
    protected Enum(String name, int ordinal) {
        this.name = name;
        this.ordinal = ordinal;
    }
    
    1. 测试
    /**
     * @Author: CaoJun
     * @Description:
     * @Create: 2020-01-17 23:10
     **/
    public class EnumTest {
        public static void main(String[] args) {
            try {
                Class clazz = EnumSingleton.class;
                Constructor c = clazz.getDeclaredConstructor(String.class, int.class);
                c.setAccessible(true);
                EnumSingleton enumSingleton = (EnumSingleton) c.newInstance("Tom", 666);
            } catch (Exception e) {
                e.printStackTrace();
            }
        }
    }
    
    1. 执行结果: 告诉我们不能用反射来创建枚举类型
    在这里插入图片描述
    1. 进入 JDK 源码中的 Constructor#newInstance() 方法中做了强制性的判断,如果修饰符是Modifier.ENUM 枚举类型, 直接抛出异常
    /**
     * Constructor#newInstance()
     */
    public T newInstance(Object ... initargs)
        throws InstantiationException, IllegalAccessException,
    IllegalArgumentException, InvocationTargetException
    {
        if (!override) {
            if (!Reflection.quickCheckMemberAccess(clazz, modifiers)) {
                Class<?> caller = Reflection.getCallerClass();
                checkAccess(caller, clazz, null, modifiers);
            }
        }
        if ((clazz.getModifiers() & Modifier.ENUM) != 0)
            throw new IllegalArgumentException("Cannot reflectively create enum objects");
        ConstructorAccessor ca = constructorAccessor;   // read volatile
        if (ca == null) {
            ca = acquireConstructorAccessor();
        }
        @SuppressWarnings("unchecked")
        T inst = (T) ca.newInstance(initargs);
        return inst;
    }
    

    相关文章

      网友评论

          本文标题:设计模式之单例模式

          本文链接:https://www.haomeiwen.com/subject/oylkzctx.html