zoukankan      html  css  js  c++  java
  • 单例模式

    单例模式

    饿汉模式

    public class Singleton {
    
        private static Singleton singleton  = new Singleton();
    
        private Singleton() {
        }
        public static Singleton getInstance(){
            return singleton;
        }
    }
    

    此方法能保证singleton唯一性,但一起动则初始化,如果有大量的动作,那么会极为耗费性能,所以引申出懒汉模式。

    懒汉模式

    public class Singleton {
    
        private static volatile Singleton singleton;
    
        private Singleton() {
        }
        public synchronized static Singleton getInstance(){
            if (singleton == null){
                singleton = new Singleton();
            }
            return singleton;
        }
    }
    

    此方法能保证唯一性,而且系统初始化时也不会耗费性能,只有第一次调用才会耗费性能,但由于使用了synchronized,所以getInstance方法效率很低。

    DCL单例模式

    懒汉模式是这样的

    /**
     * 普通的单例模式
     * @author
     */
    public class Singleton {
    
        private static Singleton singleton;
    
        private Singleton(){}
    
        private synchronized static Singleton getInstance(){
            if (singleton == null){
                singleton = new Singleton();
            }
            return singleton;
        }
    }
    

    又或者是这样的

     private static Singleton getInstance(){
            synchronized(Singleton.class){
                if (singleton == null){
                    singleton = new Singleton();
                }
                return singleton;
            }
        }
    

    但实质上synchronized放在方法上和代码块上的作用对于同步而言是一样的,都是使其包裹区域同一时间内只有一个线程可以运行(这是原子性),但是我们细心观察发现,我们其实需要保证同步的代码只有“singleton = new Singleton()”这一行代码,而且我们synchronized会造成性能损耗,那么对于这种情况,我们应该有更好的方法,那么就到了我们的DCL(double check locking)单例模式

    DCL单例模式

     private static Singleton getInstance() {
            if (singleton == null) {
                synchronized (Singleton.singleton) {	// flag1
                    if (singleton == null){				// flag2
                        singleton = new Singleton();
                    }
                }
            }
            return singleton;
        }
    

    这样以后,只有第一个初始化的时候才会进入同步代码块,如果第一次初始化有thread1和thread2执行到flag1代码后,thread1释放完锁后thread2进入并不会再次new实例,这就保证了唯一性

    这样看似很完美,但事实并不如此,我们先来看singleton = new Singleton被编译为指令后做了什么操作,做了三步操作:

    • 1、给singleton实例分配内存
    • 2、执行初始化构造方法
    • 3、将instance指向了实际分配的内存空间。

    注意,在3这一步的时候因为singleton引用指向了实际的内存空间,所以它不为null了

    但jvm指令重拍会造成一个情况,会造成2和3的顺序重排,原因是为了优化指令,所以实际上的指令顺序是这样的

    • 1、给singleton实例分配内存
    • 3、将instance指向了实际分配的内存空间
    • 2、执行初始化构造方法

    然后当执行3完后,如果有另一个线程此时调用getInstance方法,那么第一行的判断则不成立,会返回singleton,此时返回的singleton是未初始化完的,那么使用肯定会报错“实例未初始化”,为解决此问题则引入一个volatile关键字,此关键字修饰的变量不会被指令重排(这是有序性,除此之外volatile还保证了可见性),注意volatile修饰的变量不会被指令重排,但其他的代码会被指令重排,但指令重排不会影响被volatile修饰的变量的代码顺序

    所以使用volatile修饰

    private static volatile Singleton singleton;
    

    完整代码如下

    public class Singleton {
    
        private static volatile Singleton singleton;
    
        private Singleton() {
        }
    
        private static Singleton getInstance() {
            if (singleton == null) {
                synchronized (Singleton.singleton) {
                    if (singleton == null) {
                        singleton = new Singleton();
                    }
                }
            }
            return singleton;
        }
    }
    

    最后做个总结:单例模式最初有饿汉懒汉的区分,但懒汉由于一般使用synchronized代码块修饰导致性能降低,所以我们更改了synchronized的位置保证只影响第一次初始化时的性能,然后又由于jvm指令重排的问题,我们引入了volatile关键字

    我们思考一下,能不能使用其他的方法来实现第一次调用时才加载,且不使用synchronized机制呢?答案很显然有,就是Holder单例模式,它使用了静态内部类的方法

    Holder单例模式

    DCL是为了解决懒汉模式中存在的性能问题,但它的代码逻辑有些许复杂,我们可以用更简单的方式来实现

    public class Singleton {
    
        private static class InSingleton{
            private static InSingleton inSingleton = new InSingleton();
        }
    
        public static InSingleton getInstance(){
            return InSingleton.inSingleton;
        }
    }
    

    为什么这个能保证唯一性,且能保证延时加载,为啥呢?

    首先是延时加载的问题:

    当我们的系统启动时Singleton被JVM加载,但此时我们内部类InSingleton并没有被加载,这是延时加载,当getInstance方法被调用的时候,InSingleton就被初始化了

    然后是唯一性问题:

    jvm解释中,虚拟机会保证一个类的init方法被正确的加锁,当多个线程同时去init的时候,只有一个线程会进入init方法,其他线程则会被阻塞,这就保证了唯一性

    虽说Holder看起来是比较完美的单例模式,既解决了延时加载,也解决了唯一性安全的问题,但由于是内部类创建的实例,所以如果你想创建实例的时候根据传入参数来创建的话,那么是无法完成的,所以根据这一点来说,DCL和Holder自行评估使用

    枚举单例模式

    public enum SingletonEnum {
        /**
         * 需要的对象
         */
        INSTANCE
    }
    

    枚举单例模式特别简洁,且里面同样可以拥有属性方法(因为编译后枚举也是一个类,只是继承与Enum),且同样能保证唯一性,还有防止序列化和反射的不一致情况,具体可百度,太多不想写了

  • 相关阅读:
    HttpMessageNotWritableException: Could not write JSON: No serializer found for class ****
    在线测试且生成文档
    jenkins和gitlab结合的时候出错
    Fatal error: Allowed memory size of 134217728 bytes exhausted (tried to allocate 2611816 bytes)
    webpack初入
    破网速测试
    SQLDumpSplitter sql文件分割工具
    FTP连接服务器总报错的问题解决
    nw.js---创建一个点击菜单
    nw.js---开发一个百度浏览器
  • 原文地址:https://www.cnblogs.com/daihang2366/p/14480197.html
Copyright © 2011-2022 走看看