单例模式是最常见的一种设计模式,他的意思就是一个类只能创建一个实例对象出来。单例模式又分为懒汉式和饿汉式,第一个为这两种单例模式起名字的人是怎么想出来这个名字的。。。不过也挺好理解的,没有故意起一些听上去很厉害的名词。
饿汉式单例模式代码如下:
饿汉式单例饿汉式单例其实很简单,直接在类定义一个私有的静态实例就好了,这样一来在类加载的初始化的时候jvm就是执行<clinit>方法创建一个实例,jvm保证<clinit>方法只会被执行一次。所以是不会存在线程安全的问题的。但是有人说我都还没用呢就已经创建出来了,能不能在使用到的时候在创建呢。于是懒汉式就应运而生。懒汉懒汉就是这个类非常懒,只有你向他要对象的时候才给你。
懒汉式第一版:
根据饿汉式改进的似乎应该是这个样子的,但是,在单线程的情况下是没有任何问题的。如果是多线程呢,很显然并不能保证单例。那怎么办呢直接加锁效率比较低,于是就出现了DCL(double check lock)单例也就是双重检查锁。
这种双重检测锁在多个线程都判断对象为空时,即使他们争取到了锁也要再次判断对象是否为空,保证了单例。这样看起来非常好既满足了单例,还保证了线程安全。
但是在在创建对象的时候会有指令重拍的问题出现,还是有可能出现问题,尽管可能性非常小。
创建对象全过程java创建对象时分为很多步的,如果按照这个顺序来一起都很顺利。但是cpu为了提高执行效率会产生指令重排序比如上面的(6)放在了(4)后面执行,这样在单线程下由于是串行执行的不会发生什么问题。但在DCL多线程的时候就有可能发生问题。
指令重排的问题thread1在执行到第六步的时候已经将对象的引用赋给了singleten,那么singleton就不为null了,但是在这个时候thread2执行的时候发现singleton不为空就拿到singleton兑现直接使用了,但是此时的成员变量还只是初始值,并不是真正的值。如果thread2在调用成员变量时thread1已经执行了<init>方法,成员变量已经是正确值了,不会有数据不一致问题,但是thread1在thread2执行<init>方法之前调用了成员变量呢,这样数据就有问题了。
此时,volatile就可以发挥作用了,volatile有两个作用保证线程可见性和禁止指令重排序,我们只需要使用volatile不让程序发生指令重排序就完美解决问题。
最终版DCL单例这样就保证了只有在对象完全创建的时候,别的线程才能拿到该对象,就不会出现调用成员变量时数据不正确的问题了。
懒汉式单例出现了这么多问题,可见一定不要懒。
网友评论