单例模式

作者: 垃圾简书_吃枣药丸 | 来源:发表于2019-04-03 10:49 被阅读6次

    # 单例模式需要满足:

    1. 私有的构造函数
    2. 懒加载
    3. 线程安全
    4. 通过静态方法来访问实例
    5. 无法通过反射来实例化对象
    6. 无法通过反序列化来实例化对象

    1. 饿汉模式

    package com.futao.springbootdemo.design.pattern.gof.a.singleton;
    
    /**
     * 单例模式1-饿汉模式,即在类加载的时候就实例化对象。
     *
     * @author futao
     * Created on 2018-12-25.
     */
    public class EagerSingleton {
        /**
         * 因为该字段是静态的,属于类,所以会在类加载的时候就初始化,
         * 又因为类加载的时候是天然的线程安全的,所以不会有线程安全问题
         * <p>
         * 伴随着类的加载而实例化一个对象,如果该单例最后并未被使用,则浪费了系统资源
         */
        private static final EagerSingleton instance = new EagerSingleton();
    
        /**
         * 私有构造方法,防止用户随意new对象
         */
        private EagerSingleton() {
        }
    
        /**
         * 获取单例的静态方法,对于需要频繁访问的对象使用这种方式比较好
         * 为什么不设置成final的,因为静态方法没必要设置成final的
         * 
         * @return 单例
         */
        public static EagerSingleton getInstance() {
            return instance;
        }
    }
    
    

    2. 懒汉模式

    package com.futao.springbootdemo.design.pattern.gof.a.singleton;
    
    import java.io.Serializable;
    
    /**
     * 单例模式2-懒汉模式
     * 只有在用到的时候才实例化对象
     *
     * @author futao
     * Created on 2018-12-25.
     */
    public class LazySingleton {
        private static LazySingleton instance;
    
        /**
         * 私有构造方法
         */
        private LazySingleton() {}
    
        /**
         * 会有线程安全问题,所以需要加上同步锁synchronized
         * 因为这种方式,同时只能被一个线程访问,其他线程都会被阻塞,所以多线程环境下获取对象的速度非常慢
         *
         * @return 单例对象
         */
        public static synchronized LazySingleton getInstance() {
            if (instance == null) {
                instance = new LazySingleton();
            }
            return instance;
        }
    }
    
    

    3. 枚举模式

    package com.futao.springbootdemo.design.pattern.gof.a.singleton;
    
    /**
     * 单例模式3-枚举式
     * 避免了反射与反序列化的漏洞
     * 但是没有懒加载的效果
     *
     * @author futao
     * Created on 2018-12-25.
     */
    public enum SingletonEnum {
    
        /**
         * 这个枚举元素本身就是单例的
         */
        INSTANCE;
    
        private int field;
    
        /**
         * 枚举也可以有普通成员方法
         *
         * @param words
         */
        public void say(String words) {
            System.out.println(words);
        }
    
        public int getField() {
            return field;
        }
    
        public void setField(int field) {
            this.field = field;
        }}
    
    

    4. 静态内部类模式(静态内部类实现的单例无法防止反射)

    package com.futao.springbootdemo.design.pattern.gof.a.singleton.byself;
    
    /**
     * 单例模式4-静态内部类
     * 线程安全,调用效率高,并且实现了延时加载
     *
     * @author futao
     * Created on 2019-04-03.
     */
    public class StaticInnerClassSingleton {
    
        private StaticInnerClassSingleton() {
        }
    
        /**
         * 静态内部类并不会在类一开始加载的时候就加载
         * 要等到真正调用的时候才会加载
         * 又因为类加载是天然的线程安全的,所以不会有线程安全问题
         */
        private static class StaticInnerClass {
            private static final StaticInnerClassSingleton instance = new StaticInnerClassSingleton();
        }
    
        public static StaticInnerClassSingleton getInstance() {
            return StaticInnerClass.instance;
        }
    }
    
    
    =================================================
    测试内部静态类的加载时机
    =================================================
    
    package com.futao.springbootdemo.design.pattern.gof.a.singleton;
    
    import org.apache.commons.lang3.StringUtils;
    
    /**
     * 测试内部静态类的加载时机
     *
     * @author futao
     * Created on 2019-04-02.
     */
    public class InnerStaticClassLoaderOrder {
        static {
            System.out.println("外部类被加载");
        }
    
        public void outerMethod() {
            System.out.println("调用外部类方法");
        }
    
        /**
         * 内部静态类
         */
        private static class InnerStaticClass {
    
            static int a;
            int b;
    
            static {
                System.out.println("内部类被加载了");
            }
    
            public void innerMethod() {
                System.out.println("调用静态类内部方法");
            }
        }
    
        /**
         * 内部类
         */
        private class InnerClass {
            //        static int a;//普通内部类不允许有静态成员
            int b;
    
            public void innerClassMethod() {
                System.out.println("println");
            }
        }
    
        public static void main(String[] args) {
            InnerStaticClassLoaderOrder i = new InnerStaticClassLoaderOrder();
            i.outerMethod();
            System.out.println(StringUtils.repeat("==", 30));
            //静态内部类通过new 外部类类名.内部类类名()的方式实例化对象
            InnerStaticClassLoaderOrder.InnerStaticClass innerStaticClass = new InnerStaticClassLoaderOrder.InnerStaticClass();
            innerStaticClass.innerMethod();
    
            /*
            输出为:外部类被加载
                    调用外部类方法
                    ============================================================
                    内部类被加载了
                    调用静态类内部方法
    
    
                    说明内部静态类不会随着外部类的加载而加载,而是等到被实际调用的时候才加载
                             */
    
            InnerClass innerClass = i.new InnerClass();//普通内部类只能通过外部类对象.new 内部类()来实例化对象
            innerClass.innerClassMethod();
        }
    }
    
    

    # 测试

     @Test
        public void test75() {
            System.out.println(EagerSingleton.getInstance());
            System.out.println(EagerSingleton.getInstance());
            System.out.println(EagerSingleton.getInstance());
            System.out.println(StringUtils.repeat("==", 30));
            System.out.println(LazySingleton.getInstance());
            System.out.println(LazySingleton.getInstance());
            System.out.println(LazySingleton.getInstance());
            System.out.println(StringUtils.repeat("==", 30));
            System.out.println(SingletonEnum.INSTANCE == SingletonEnum.INSTANCE);
            System.out.println(StringUtils.repeat("==", 30));
            System.out.println(StaticInnerClassSingleton.getInstance());
            System.out.println(StaticInnerClassSingleton.getInstance());
            System.out.println(StaticInnerClassSingleton.getInstance());
        }
    
    单例模式测试

    # 如何防止反射来实例化对象

    • 首先看看如何通过反射创建一个对象
     @SuppressWarnings("unchecked")
        @Test
        public void test76() throws Exception {
            //通过静态方法访问单例对象
            System.out.println(EagerSingleton.getInstance());
            System.out.println(EagerSingleton.getInstance());
    
            Class<EagerSingleton> eagerSingleton = (Class<EagerSingleton>) Class.forName("com.futao.springbootdemo.design.pattern.gof.a.singleton.EagerSingleton");
            //获取构造方法
            Constructor<EagerSingleton> constructor = eagerSingleton.getDeclaredConstructor();
            //因为构造方法是私有的,所以需要跳过java安全检查
            constructor.setAccessible(true);
            //通过反射创建新的对象
            EagerSingleton singleton = constructor.newInstance();
            System.out.println(singleton);
        }
    
    反射创建对象
    • 这样就破坏了对象的单例。但是从中可以看出,反射是通过调用构造方法来实例化对象的,所以考虑在构造方法进行拦截。
        /**
         * 私有构造方法,防止用户随意new对象
         */
        private EagerSingleton() {
            if (instance != null) {
                //如果单例对象已经被创建,则不允许再调用构造方法创建对象
                throw new RuntimeException("不允许通过反射创建对象!");
            }
        }
    

    # 如何防止反序列化来实例化对象

    • 如何通过反序列化来创建一个对象

    首先如果要序列化与反序列化需要 implements Serializable

    • 序列化对象
        //序列化对象
        @Test
        public void test77() throws Exception {
            ObjectOutputStream objectOutputStream = new ObjectOutputStream(new FileOutputStream("./a.txt"));
            objectOutputStream.writeObject(EagerSingleton.getInstance());
        }
    
    • 反序列化对象
      @Test
        public void test77() throws Exception {
            //通过静态方法访问单例对象
            System.out.println(EagerSingleton.getInstance());
            System.out.println(EagerSingleton.getInstance());
            //反序列化对象
            ObjectInputStream objectInputStream = new ObjectInputStream(new FileInputStream("./a.txt"));
            EagerSingleton eagerSingleton = (EagerSingleton) objectInputStream.readObject();
            System.out.println(eagerSingleton);
        }
    
    反序列化破坏单例
    • 反序列化破坏单例的解决方案
      在单例类中添加方法readResolve()
        /**
         * 防止反序列化创建对象
         * 在jdk中ObjectInputStream的类中有readUnshared()方法,
         * 如果被反序列化的对象的类存在readResolve这个方法,
         * 他会调用这个方法来返回一个“array”
         * 然后浅拷贝一份,作为返回值,并且无视掉反序列化的值,即使那个字节码已经被解析。
         *
         * @return
         */
        private Object readResolve() {
            return instance;
        }
    
    • 再次执行上面的测试


      image.png

    # 各种单例模式效率测试

     @Test
        public void test74() throws InterruptedException {
            int threadCount = 10;
            long start = System.currentTimeMillis();
    
            CountDownLatch countDownLatch = new CountDownLatch(threadCount);
    
            //开启10个线程
            for (int i = 0; i < threadCount; i++) {
                new Thread(() -> {
                    //10个线程并发获取单例对象1000W次
                    for (int j = 0; j < 10000000; j++) {
                        Object o = EagerSingleton.getInstance();
                    }
                    //一个线程执行完成之后计数器-1
                    countDownLatch.countDown();
                }).start();
            }
            //阻塞主线程进行等待,内部会一直检查计数器的值是否为0
            countDownLatch.await();
            long end = System.currentTimeMillis();
            System.out.println(end - start);
        }
    
    • 饿汉模式
    饿汉模式
    • 懒加载模式
    懒加载模式
    • 枚举模式
    枚举模式
    • 静态内部类模式
    静态内部类模式
    单例模式名称 测试线程数 单个线程访问对象次数 耗时
    饿汉模式 10 1000W 165ms
    懒汉模式 10 1000W 5750ms
    枚举式 10 1000W 120ms
    静态内部类 10 1000W 115ms

    相关文章

      网友评论

        本文标题:单例模式

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