美文网首页
单例模式详解

单例模式详解

作者: 奋斗的韭菜汪 | 来源:发表于2020-06-15 23:33 被阅读0次

    应用场景:是指确保一个类在任何情况下都绝对只有一个实例(实现:隐藏其所有的构造方法),并提供一个全局访问点(实现:getInstance方法)
    源码应用点:ApplicationContext\servletContext\DBPool
    单例可以分为四类:
    1、饿汉式单例
    2、懒汉式单例
    3、注册式单例(其中最优雅的写法:枚举式单例)
    4、ThreadLocal单例

    private Object readResolve(return INSTANCE);
    加上这个方法可以防止序列号和反序列化破坏单例(原因:反序列化获取的对象默认是readResolve通过浅拷贝,拷贝的是对象的引用,所以还是同一个实例对象)

    ThreadLocal单例:在同一个线程内会的实例是相同的,ThreadLocal中有个重要对象ThreadLocalMap,它的key是this就是当前线程,纬度是一线程为单位
    单例模式的缺点:没有接口扩展困难(违背开闭原则的)

    重点总结:
    1、私有化构造器
    2、保证线程安全
    3、延迟加载
    4、防止序列化和反序列化破坏单例
    5、防止反射破坏单例
    1、饿汉式写法一

    /**
     * 1、饿汉式写法一:类加载时就初始化
     * 优点:执行效率高,性能高(没有加任何锁)
     * 缺点:内存浪费(在某些情况下:在单例大量使用的情况);在类的加载时才初始化 
     * 会保证内存利用率高
     */
    public class HungrySigleton {
        /**
         * 类加载时就初始化
         */
        private static final HungrySigleton hungrySigleton = new HungrySigleton();
        //构造函数私有化
        private HungrySigleton(){
        }
        //提供一个全局访问点
        public static HungrySigleton getHungrySigleton() {
            return hungrySigleton;
        }
    }
    

    2、饿汉式单例写法二

    /**
     * 2、饿汉式单例写法二:
     */
    public class HungryStaticSingleton {
        /**
         * 类加载顺序:
         * //先静态后动态
         * //先上,后下
         * //先属性,后方法
         */
        private static final HungryStaticSingleton hungryStaticSingleton;
        /**
         * 使用静态代码块复制,和写法一基本类似
         */
        static {
            hungryStaticSingleton = new HungryStaticSingleton();
        }
        public static HungryStaticSingleton getHungryStaticSingleton() {
            return hungryStaticSingleton;
        }
    }
    

    3、懒汉模式:外部需要时才实例化

    /**
     * 3、懒汉模式:外部需要时才实例化
     * 优点:节省内存(需要时才实例化)
     * 缺点:线程不安全(多线程调用getInstance方法,有可能创建多个实例,单例失败)
     */
    public class LazySimpleSingleton {
        //声明一个LazySimpleSingleton对象自己的全局变量
        private LazySimpleSingleton instance;
        //构造函数私有化
        private LazySimpleSingleton(){
        }
        //提供一个全局访问点
        public LazySimpleSingleton getInstance(){
            if (instance == null){
                instance = new LazySimpleSingleton();
            }
            return instance;
        }
        //例如两个线程同时调用getInstance,运行结果:
        //出现一个实例:1、正常顺序执行,2、后者覆盖前者情况
        //出现两个实例:同时进入条件,按顺序发返回
        //多线程调试:先断点,右击断点选择thread,执行方法,选择控制台Debugger下Frames,查看线程各个线程,选择执行哪个线程(
        // 线程执行顺序可以随意控制),
    }
    
    /**
     * 4、
     * 优点:节省了内存,线程安全
     * 缺点:加锁造成了性能瓶颈
     */
    public class LazySimpleLockSingleton {
        //声明一个LazySimpleSingleton对象自己的全局变量
        private LazySimpleLockSingleton instance;
        //构造函数私有化
        private LazySimpleLockSingleton(){
        }
        //提供一个全局访问点
        public synchronized LazySimpleLockSingleton getInstance(){
            //java8之后可以用juc的重入锁
            //Lock lock = new ReentrantLock();
            //lock.lock();
            if (instance == null){
                instance = new LazySimpleLockSingleton();
            }
            //lock.unlock();
            return instance;
        }
        //例如两个线程同时调用getInstance,运行结果:
        //出现一个实例:1、正常顺序执行,2、后者覆盖前者情况
        //出现两个实例:同时进入条件,按顺序发返回
        //多线程调试:先断点,右击断点选择thread,执行方法,选择控制台Debugger下Frames,查看线程各个线程,选择执行哪个线程(
        // 线程执行顺序可以随意控制),
    }
    

    5、双重检查

    /**
     * 5、双重检查
     * 优点:节省了内存,线程安全,性能高了
     * 缺点:可读性不好,不够优雅
     */
    public class LazyDoubleSimpleSingleton {
        //声明一个LazySimpleSingleton对象自己的全局变量
        private volatile LazyDoubleSimpleSingleton instance;
        //构造函数私有化
        private LazyDoubleSimpleSingleton(){
        }
        //提供一个全局访问点
        public LazyDoubleSimpleSingleton getInstance(){
            //第一次检查是否需要阻塞
            if (instance == null) {
                synchronized (this) {
                    //第二次检查是否需要阻塞
                    if (instance == null) {
                        instance = new LazyDoubleSimpleSingleton();
                        //多线程情况下,有指令重排序问题(需要在声明全局变量的时候加上volatile)
                    }
                }
            }
            return instance;
        }
    }
    

    6、

    /**
     * 6
     * classPath:LazyStaticInnerClassSingleton.class(1)
     *           LazyStaticInnerClassSingleton$LazyHolder.class(2)
     *           加载类1时不会加载类2,只用当外部使用到getInstance时才会触发加载类2
     * 优点:写法优雅,利用了Java本身的语法特点(性能高,避免了内存浪费,不会有指令重拍问题)
     * 缺点:能够被反射破坏(前面5个也能被反射破坏,为什么:前面六种都是私有化构造函数,但是反射能够从外部拿到类的私有化构造
     * 通过设置构造方法setAccessible为true,强制访问私有构造方法)
     */
    public class LazyStaticInnerClassSingleton {
        private LazyStaticInnerClassSingleton(){
            //改进方法
            if (LazyHolder.INSTANCE != null) {
                throw new RuntimeException("不允许非法访问");
            }
        }
        private static LazyStaticInnerClassSingleton getInstance(){
            return LazyHolder.INSTANCE;
        }
    
        //静态内部类(静态变量在加载的时候就会被分配内存空间,静态内部类不一样,只在使用类时才分配内存)
        private static class LazyHolder{
            private static final LazyStaticInnerClassSingleton INSTANCE = new LazyStaticInnerClassSingleton();
        }
    }
    
    /**7、
     * 枚举式单例(最优雅的写法)
     * 不能用反射创建枚举对象
     * 内存浪费(在某些情况下:在单例大量使用的情况)
     */
    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;
        }
    }
    

    相关文章

      网友评论

          本文标题:单例模式详解

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