• 单例模式


    单例模式可以分为懒汉式和饿汉式

    懒汉式单例模式:在类加载时不初始化,不调用不初始化,一旦调用就只初始化一次。

    饿汉式单例模式:在类加载时就完成了初始化,所以类加载比较慢,但获取对象的速度快。

    饿汉式第一种:基于类加载机制避免了多线程的同步问题(JVM内部的机制能够保证当一个类被加载的时候,这个类的加载过程是线程互斥的)

    缺点:类加载时创建实例,比较消耗系统资源(要是应用没有使用这个实例,系统资源就白白浪费了)

    /**
     * 单例模式:饿汉式,第一种
     * 缺点:类加载时创建实例,比较消耗系统资源(要是应用没有使用这个实例,系统资源就白白浪费了)
     *
     * @author :liuqi
     * @date :2018-06-13 15:13.
     */
    public class SingletonDemo1 {
        private static SingletonDemo1 instance = new SingletonDemo1();
    
        private SingletonDemo1() {
        }
    
        public static SingletonDemo1 getInstance() {
            return instance;
        }
    }

    为了避免这种情况,我们通常使用惰性加载的机制,也就是在使用的时候才去创建。

    懒汉式第一种:不能在多线程下使用

    缺点:线程不安全

    /**
     * 单例模式:懒汉式,第一种
     * 缺点:线程不安全
     *
     * @author :liuqi
     * @date :2018-06-13 15:17.
     */
    public class SingletonDemo2 {
        private static SingletonDemo2 instance;
    
        private SingletonDemo2() {
        }
    
        public static SingletonDemo2 getInstance() {
            if (instance == null) {
                instance = new SingletonDemo2();
            }
            return instance;
        }
    }

    为防止多线程情况下创建多个实例的问题(A线程和B线程都进入if判断),使用Class锁机制

    懒汉式第二种:在getinstance方法上加synchronized锁

    缺点:线程安全但是很影响性能,每次调用getInstance方法的时候都必须获得Singleton的锁,而实际上,当单例实例被创建以后,其后的请求没有必要再使用互斥机制了

    /**
     * 单例模式:懒汉式,第二种
     * 缺点:线程安全但是很影响性能,每次调用getInstance方法的时候都必须获得Singleton的锁,而实际上,当单例实例被创建以后,其后的请求没有必要再使用互斥机制了
     *
     * @author :liuqi
     * @date :2018-06-13 15:20.
     */
    public class SingletonDemo3 {
        private static SingletonDemo3 instance;
    
        private SingletonDemo3() {
        }
    
        public static synchronized SingletonDemo3 getInstance() {
            if(instance == null){
                instance = new SingletonDemo3();
            }
            return instance;
        }
    }

    有人为了解决以上问题,提出了double-checked locking(双重检验锁)的解决方案

    懒汉式第三种:双重检验锁

    缺点:看似已经解决上述问题,但是对于jvm来说仍然可能会发生错误,在JDK1.5之后,双重检查锁定才能够正常达到单例效果

    /**
     * 单例模式:懒汉式,第三种,双重检验锁
     * 缺点:看似已经解决上述问题,但是对于jvm来说仍然可能会发生错误,在JDK1.5之后,双重检查锁定才能够正常达到单例效果
     *
     * @author :liuqi
     * @date :2018-06-13 15:26.
     */
    public class SingletonDemo4 {
        private static SingletonDemo4 instance;
    
        private SingletonDemo4() {
        }
    
        public static synchronized SingletonDemo4 getInstance() {
            if(instance == null){
                synchronized (instance){
                    if (instance == null){
                        instance = new SingletonDemo4();
                    }
                }
            }
            return instance;
        }
    }

    这样避免了进入synchronized块所需要花费的资源,其次如果两个线程同时进入了第一个if判断,那么他们也必须按照顺序执行synchronized块中的代码,第一个进入代码块的线程会创建一个新的Singleton实例,而后续的线程则因为无法通过if判断,而不会创建多余的实例。上述描述似乎已经解决了我们面临的所有问题,但实际上,从JVM的角度讲,这些代码仍然可能发生错误。

    对于JVM而言,它执行的是一个个Java指令。在Java指令中创建对象和赋值操作是分开进行的,也就是说instance = new Singleton();语句是分两步执行的。但是JVM并不保证这两个操作的先后顺序,也就是说有可能JVM会为新的Singleton实例分配空间,然后直接赋值给instance成员,然后再去初始化这个Singleton实例。这样就使出错成为了可能,我们仍然以A、B两个线程为例:

    1.         A、B线程同时进入了第一个if判断

    2.         A首先进入synchronized块,由于instance为null,所以它执行instance = new Singleton();

    3.         由于JVM内部的优化机制,JVM先画出了一些分配给Singleton实例的空白内存,并赋值给instance成员(注意此时JVM没有开始初始化这个实例),然后A离开了synchronized块。

    4.         B进入synchronized块,由于instance此时不是null,因此它马上离开了synchronized块并将结果返回给调用该方法的程序。

    5.         此时B线程打算使用Singleton实例,却发现它没有被初始化,于是错误发生了。

    于是我们想到了通过内部类实现多线程环境中的单例模式

    懒汉式第四种:无参构造方法,静态内部类

    /**
     * 单例模式:懒汉式,第四种,无参构造方法,静态内部类
     *
     * @author :liuqi
     * @date :2018-06-13 14:15.
     */
    public class SlackerSingletonNoParamStructure {
        /**
         * 私有构造方法
         */
        private SlackerSingletonNoParamStructure() {
        }
        /**
         * 创建内部类
         */
        private static class ExampleChild {
            // 内部类加载时初始化一次
            private static final SlackerSingletonNoParamStructure newInstance = new SlackerSingletonNoParamStructure();
        }
        /**
         * 提供公有访问函数
         *
         * @return SlackerSingletonNoParamStructure
         */
        public static SlackerSingletonNoParamStructure getInstance() {
            // 调用内部类 进行初始化对象操作
            return ExampleChild.newInstance;
        }
        /**
         * 测试
         *
         * @param args
         */
        public static void main(String[] args) {
            // 新建5个线程,测试getInstance方法是否获取同一个SlackerSingletonNoParamStructure对象
            for (int i = 0; i < 5; i++) {
                new Thread(new Runnable() {
                    @Override
                    public void run() {
                        System.out.println(SlackerSingletonNoParamStructure.getInstance());
                    }
                }).start();
            }
        }
    }

    懒汉式第五种:有参构造方法,静态内部类

    /**
     * 单例模式:懒汉式,第五种,有参构造方法,静态内部类
     *
     * @author :liuqi
     * @date :2018-06-13 14:42.
     */
    public class SlackerSingletonParamStructure {
        /**
         * 私有构造方法:有参
         */
        private SlackerSingletonParamStructure(String str) {
            System.out.println(str);
        }
        /**
         * 创建内部类
         */
        private static class ExampleChild {
            // 内部类加载时初始化一次
            private static final SlackerSingletonParamStructure newInstance = new SlackerSingletonParamStructure("testInfo");
        }
    
        /**
         * 提供公有访问函数
         *
         * @return SlackerSingletonParamStructure
         */
        public static SlackerSingletonParamStructure getInstance() {
            // 调用内部类 进行初始化对象操作
            return ExampleChild.newInstance;
        }
    
        /**
         * 测试
         *
         * @param args
         */
        public static void main(String[] args) {
            // 新建5个线程,测试getInstance方法是否获取同一个SlackerSingletonParamStructure对象
            for (int i = 0; i < 5; i++) {
                new Thread(new Runnable() {
                    @Override
                    public void run() {
                        System.out.println(SlackerSingletonParamStructure.getInstance());
                    }
                }).start();
            }
        }
    }

    饿汉式第二种:静态内部类

    /**
     * 单例模式:饿汉式写法,静态内部类
     *
     * @author :liuqi
     * @date :2018-06-13 15:00.
     */
    public class HungrySingleton {
        /**
         * 程序启动时就加载创建对象
         */
        private static final HungrySingleton newInstance = new HungrySingleton();
    
        /**
         * 私有构造方法
         */
        private HungrySingleton() {
        }
        /**
         * 提供公有访问函数
         *
         * @return Example
         */
        public static HungrySingleton getInstance() {
            return newInstance;
        }
    
        /**
         * 测试
         *
         * @param args
         */
        public static void main(String[] args) {
            // 新建5个线程,测试getInstance方法是否获取同一个HungrySingleton对象
            for (int i = 0; i < 5; i++) {
                new Thread(new Runnable() {
                    @Override
                    public void run() {
                        System.out.println(HungrySingleton.getInstance());
                    }
                }).start();
            }
        }
    }

    参考:https://www.cnblogs.com/jingpeipei/p/5771716.html

    https://www.cnblogs.com/Ycheng/p/7169381.html

    http://www.private-blog.com/2017/11/16/%E8%AE%BE%E8%AE%A1%E6%A8%A1%E5%BC%8F%EF%BC%9A%E5%8D%95%E4%BE%8B/

    代码地址:https://github.com/yuki9467/TST-javademo/tree/master/src/main/singleton

  • 相关阅读:
    win10下jdk8和jdk11切换的批处理脚本
    Deeping中使用python连接Oralce报错:Cannot locate a 64-bit Oracle Client library: "./instantclient_21_1/libclntsh.so: file too short"
    Redis作者“不懂”分布式锁【转载】
    搭建nacos高可用集群
    使用spring.config.location与本地配置文件属性不能互补
    protocol buffer应用场景方案想法
    protocol buffer 入门和基本知识
    IDEA快捷键
    软考问题总结
    pandas 使用问题记录
  • 原文地址:https://www.cnblogs.com/yuki67/p/9178284.html
Copyright © 2020-2023  润新知