• 单例模式


    什么是单例模式

      一个对象在应用程序中只存在一个。

    单例实现原理

      以java为例,私有构造器,一个私有的静态的本身实例,一个对外开放获取本身实例的静态公共方法。

    多种不同的方式实现单例

      饿汉式

      在使用这个类时就初始化这个对象。

    package com.lxlyq.singlePattern;
    
    /**
     * 饿汉式单例
     */
    public class HungrySingle {
        // 私有对象
        private static HungrySingle hungrySingle = new HungrySingle();
        // 私有构造,防止外部new
        private HungrySingle(){}
        // 外部调用
        public static HungrySingle getHungrySingle() {
            return hungrySingle;
        }
    }
    

      缺点:可能没有使用这个对象,但应用程序中也存在这个对象,浪费内存。

      懒汉式

      只有获取这个实例时才初始化对象。

    package com.lxlyq.singlePattern;
    
    /**
     * 懒汉式
     */
    public class LazySingle {
        // 私有对象 未初始化
        private static LazySingle lazySingle;
        // 私有构造 防止外部通过 new 创建
        private LazySingle() { }
        // 获取实例
        public static LazySingle getInstance() {
            // 不存在则创建
            if (lazySingle == null) {
                lazySingle = new LazySingle();
            }
            return lazySingle;
        }
    }
    

      优点:弥补了饿汉式的不足。

      缺点:当存在多线程的时候将存在线程安全问题,两个线程同时进入if判断语句中,将会存在两个对象。

    懒汉式同步方法锁

    package com.lxlyq.singlePattern;
    
    /**
     * 懒汉式(同步方法)
     */
    public class LazySynchronizedMethodSingle {
        // 私有对象 未初始化
        private static LazySynchronizedMethodSingle lazySingle;
        // 私有构造 防止外部通过 new 创建
        private LazySynchronizedMethodSingle() { }
        // 获取实例 加上同步锁 防止多线程创建多个实例
        public static synchronized LazySynchronizedMethodSingle getInstance() {
            // 不存在则创建
            if (lazySingle == null) {
                lazySingle = new LazySynchronizedMethodSingle();
            }
            return lazySingle;
        }
    }
    

      优点:当获取时才创建实例,不存在线程安全问题。

      缺点:每次获取实例时都需要加锁,效率极低。

    懒汉式同步块锁

    package com.lxlyq.singlePattern;
    
    /**
     * 懒汉式同步块
     */
    public class LazySynchronizedBlockSingle {
        // 私有对象 未初始化
        private static LazySynchronizedBlockSingle lazySingle;
        // 私有构造 防止外部通过 new 创建
        private LazySynchronizedBlockSingle() { }
        // 获取实例 加上同步块防止多线程创建多个实例
        public static  LazySynchronizedBlockSingle getInstance() {
            synchronized (LazySynchronizedBlockSingle.class) {
                // 不存在则创建
                if (lazySingle == null) {
                    lazySingle = new LazySynchronizedBlockSingle();
                }
            }
            return lazySingle;
        }
    }

      与同步方法锁一样,效率也差不多,每次都需要加锁。需要改进如下

    懒汉式双重校验同步块锁

    package com.lxlyq.singlePattern;
    
    /**
     * 懒汉式双重校验同步块
     */
    public class LazyDoubleCheckSynchronizedBlockSingle {
        // 私有对象 未初始化
        private static LazyDoubleCheckSynchronizedBlockSingle lazySingle;
        // 私有构造 防止外部通过 new 创建
        private LazyDoubleCheckSynchronizedBlockSingle() { }
        // 获取实例 加上同步块防止多线程创建多个实例
        public static LazyDoubleCheckSynchronizedBlockSingle getInstance() {
            if (lazySingle == null) {
                // 第一次加锁,以后都不会再进入
                synchronized (LazyDoubleCheckSynchronizedBlockSingle.class) {
                    // 不存在则创建
                    if (lazySingle == null) {
                        lazySingle = new LazyDoubleCheckSynchronizedBlockSingle();
                    }
                }
            }
            return lazySingle;
    
        }
    }
    

      优点:只有获取对象时才被创建。不存在线程安全问题。因为双重校验,只有当对象未被创建前才会被加锁,效率满足要求。

      缺点:不符合审美(瞎扯)。

    以上的真的都是单例了吗

      // 通过反射破坏

    package com.lxlyq.singlePattern;
    import java.lang.reflect.Constructor;
    public class SingleDemo {
        public static void main(String[] args) throws Exception {
            // 通过懒汉式双重校验获取实例
            LazyDoubleCheckSynchronizedBlockSingle single1 = LazyDoubleCheckSynchronizedBlockSingle.getInstance();
            System.out.println(single1);
            // 通过懒汉式双重校验获取实例
            LazyDoubleCheckSynchronizedBlockSingle single2 = LazyDoubleCheckSynchronizedBlockSingle.getInstance();
            System.out.println(single2);
            // 通过反射创建对象
            Class cls = LazyDoubleCheckSynchronizedBlockSingle.class;
            Constructor<?> cs = cls.getDeclaredConstructor();
            cs.setAccessible(true);
            LazyDoubleCheckSynchronizedBlockSingle single3 = (LazyDoubleCheckSynchronizedBlockSingle) cs.newInstance();
            System.out.println(single3);
            // out 
            // com.lxlyq.singlePattern.LazyDoubleCheckSynchronizedBlockSingle@4554617c
            // com.lxlyq.singlePattern.LazyDoubleCheckSynchronizedBlockSingle@4554617c
            // com.lxlyq.singlePattern.LazyDoubleCheckSynchronizedBlockSingle@74a14482
        }
    }
    

      之前所有的单例创建方式都存在通过反射破坏单例问题。

    解决单例破坏问题

    package com.lxlyq.singlePattern;
    
    /**
     * 禁止通过反射创建单例
     */
    public class BanReflexCreateSinge {
        // 采用饿汉式,但是客户端一样可以通过反射创建
        private static BanReflexCreateSinge banReflexCreateSinge = new BanReflexCreateSinge();
        // 防止反射
        private BanReflexCreateSinge(){
            if (banReflexCreateSinge != null) {
                throw new RuntimeException("禁止一切方法破坏单例");
            }
        }
        // 外部调用
        public static BanReflexCreateSinge getInstance() {
            return banReflexCreateSinge;
        }
    }
    

      通过这种反射,当系统中存在时就会抛出异常。

      有一种通过枚举方式创建单例,枚举的底层也是这个原理。

      这里没有介绍通过枚举创建单例,是我觉得枚举没有类那么灵活,尽管枚举是创建单例的最好方式,但不是最好的方案。

    静态内部内实现单例

    package com.lxlyq.singlePattern;
    
    /**
     * 内部类创建单例
     */
    public class LazyInnerClassSingle {
        // 防止外部调用
        private LazyInnerClassSingle(){}
        // 静态内部类
        private static class LazyInnerClass{
            private static LazyInnerClassSingle lazyInnerClassSingle = new LazyInnerClassSingle();
        }
        // 获取实例
        public static LazyInnerClassSingle getInstance() {
            return LazyInnerClass.lazyInnerClassSingle;
        }
    }
    

      这种方式,没有加锁,但也是懒汉式,代码简洁,推荐使用。

    总结

      单例优点:

    • 系统中只存在一个,减少内存开支,当一个对象需要被频繁创建销毁时,把它作为单例效果将极其明显。
    • 系统多次需要使用一个对象时,把他设为单例可以减少系统开支。(如读取的配置信息)。
    • 单例可以防止资源被多重占用,如读写文件。 

       单例缺点:

    • 不适用于变化的对象
    • 一般单例的职责都过重,违背了'单一原则'(一个类负责一件事)
    • 单例没有抽象层,难以扩展。

      

  • 相关阅读:
    《数据安全实践指南》 数据销毁安全实践数据销毁处理
    《数据安全实践指南》 数据销毁安全实践介质销毁处理
    软考信息安全网络攻击原理与常用方法
    《数据安全实践指南》 通用安全实践数据安全策略规划
    软考信息安全密码学基本理论
    Discourse 清理存储空间的方法
    Java “constant string too long” 编译错误
    Java 是否应该使用通配符导入( wildcard imports)
    Java 多行字符串
    Edge 提供了标签分组功能
  • 原文地址:https://www.cnblogs.com/tysonlee/p/11100617.html
Copyright © 2020-2023  润新知