详解单例模式-(Volatile应用)

什么是单例模式
  • 构造函数私有
  • 以静态方法或者枚举返回实例
  • 确保实例只有一个,不论是在单线程环境
  • 确保反序列化时候不会重新构建对象
常用的单例模式有

恶汉模式,懒汉模式,双重校验锁懒汉模式,静态内部类,枚举模式

恶汉模式
    public class SingleTon {
        private static SingleTon INSTANCE = new SingleTon();

        private SingleTon() {
        }

        public static SingleTon getInstance() {
            return INSTANCE;
        }
    }

懒汉模式在类被初始化就已经在内存中创建对象,以空间换区时间,不存在线程安全问题

懒汉模式
    public class SingleTon {
        private static SingleTon INSTANCE = null;

        private SingleTon() {
        }

        public static SingleTon getInstance() {
            if (INSTANCE == null) {
                INSTANCE = new SingleTon();
            }
        }
    }

​ 懒汉模式在方法被调用后才创建对象**,以时间换取空间,在多线程环境下存在风险**)(可以理解比较懒,只有用到时候才去创建)

双重锁懒汉模式(DCL)

    public class SingleTon {
        private static SingleTon INSTANCE = null;

        private SingleTon() {
        }

        public static SingleTon getInstance() {
            if (INSTANCE == null) {
                synchronized (SingleTon.class) {
                    if (INSTANCE == null) {
                        INSTANCE = new SingleTon();
                    }
                }
                return INSTANCE;
            }
        }
    }

​ DCL模式的优点就是,只有在对象需要被使用时才创建,第一次判断INSTANCE == null为了避免非必要加锁,当第一次加载时才对实例进行加锁再实例化,这样既可以节约内存空间,又可以保证线程安全,由于JVM存在指令重排的,所以DCL也会存在线程不安全的情况

双重锁懒汉模式(DCL)
    public class SingleTon {
        private static SingleTon INSTANCE = null;

        private SingleTon() {
        }
        public static SingleTon getInstance() {
            if (INSTANCE == null) {
                synchronized (SingleTon.class) {
                    if (INSTANCE == null) {
                        INSTANCE = new SingleTon();
                    }
                }
                return INSTANCE;
            }
        }
    }
  • 优点

​ DCL的优点就是只要对象被用到的时候才创建,第一次INSTANCE为空是为了避免非必要加锁,当第一次被加载时才对实例进行加载和实例化,这样既可以节约内存空间,有能保证线程安全,但是由于JVM存在指令重排的功能,DCL也会出现线程不安全的问题。具体分析如下

INSTANCE  = new SingleTon(); 

这个步骤,其实在JVM里面其实执行三步,

  1. 在堆内存中开辟内存空间
  2. 在堆内存中实例化SingTon里面的各个参数
  3. 把对象指向堆内存空间

由于JVM存在指令重排的功能,可能2还没执行,3就已经执行了,如果恰巧此时在切换到线程B上,这个时候由于已经执行了3.判断INSTANCE为非空,直接拿来用,这样的话就会出现异常,这就是著名的DCL失效问题,

怎么解决DCL失效问题

我们通过Volatile关键字修饰变量声明,禁止指令重排来解决DCL失效问题。Volatile确保INSTANCE每次均在主内存中读取,这样虽然损失一点效率但是也是无伤大雅

静态内部类
   public class SingleTon {
        private SingleTon() {
        }

        private static class SingleTonHoler {
            private static SingleTon INSTANCE = new SingleTon();
        }

        public static SingleTon getInstance() {
            return SingleTonHoler.INSTANCE;
        }
    }
  • 静态内部类的优点

    外部加载类的时候并不需要立马加载内部类,内部类不被加载则不会初始化INSTANCE,故不占内存,既当SingleTon第一次被加载的时候,并不是立马就去加载SingleTonHoler,只要当genstance()被调用的时候,才会去初始化INSTANCE,这样不仅能保证线程安全,也能保证单例的唯一性,同时推迟了线程的初始化

  • 静态内部类的缺点

​ 静态内部类无法传递参数,由于是静态内部类的去创建单例的,故外部无法传递参数。

  • 静态内部类是如何保证线程安全的
    我们先了解一下类的加载机制

​ 类加载时机,JAVA虚拟机有且仅有下列五种场景会对类进行初始化

  1. ​ 遇到new,getstatic,setstatic或者invokestatic这四个字节码指令的时候,对应的java应用场景就是:new一个关键字,或者一个实例化对象时读取或者设置一个静态字段时(final修饰,已在编译期把结果放在常量池的除外),调用一个类的静态方法的时候
  2. 使用java.lang.reflect包的方法对类进行反射调用的时候,如果类没进行初始化,需要先调用其初始化方法进行初始化
  3. 当初始化一个类时,如果其父类还未进行初始化,会先触发其父类的初始化
  4. 当虚拟机启动时,用户需要指定一个要执行的主类(包含main()方法的类),虚拟机会先初始化这个类
  5. 使用JDK 1.7等动态语言支持时,如果一个java.lang.invoke.MethodHandle实例最后的解析结果REF_getStatic、REF_putStatic、REF_invokeStatic的方法句柄,并且这个方法句柄所对应的类没有进行过初始化,则需要先触发其初始化。

这5种情况被称为是类的主动引用 (有且仅有这五种情况,JVM才会主动初始化类,除此之外,其他情况都是被动引用)

我们再来看 getInstance()方法,调用的是SingleTonHoler.INSTANCE,取的是SingleTonHoler里的INSTANCE对象,跟上面那个DCL方法不同的是,getInstance()方法并没有多次去new对象,故不管多少个线程去调用getInstance()方法,取的都是同一个INSTANCE对象,而不用去重新创建。当getInstance()方法被调用时,SingleTonHoler才在SingleTon的运行时常量池里,把符号引用替换为直接引用,这时静态对象INSTANCE也真正被创建,然后再被getInstance()方法返回出去,这点同饿汉模式 相似, 那么INSTANCE在创建过程中又是如何保证线程安全的呢

​ 虚拟机会保证一个类的()方法在多线程环境中被正确地加锁、同步,如果多个线程同时去初始化一个类,那么只会有一个线程去执行这个类的()方法,其他线程都需要阻塞等待,直到活动线程执行()方法完毕。如果在一个类的()方法中有耗时很长的操作,就可能造成多个进程阻塞(需要注意的是,其他线程虽然会被阻塞,但如果执行()方法后,其他线程唤醒之后不会再次进入()方法。同一个加载器下,一个类型只会初始化一次。),在实际应用中,这种阻塞往往是很隐蔽的。

说明参考

文章为看视频博客学习过程中总结,方便自己以后好复习

https://www.bilibili.com/video/BV18b411M7xz

http://moguit.cn/#/

https://blog.csdn.net/mnb65482/article/details/80458571

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值