单例模式:双重效验锁的懒汉实现方式

单例模式:双重效验锁的懒汉实现方式

单例模式

单例模式顾名思义就是指实例化一个对象,通过把构造方法私有化来禁止创建实例。

饿汉模式实现

饿汉模式的特点是在类加载的时候就创建并初始化一个实例,实例在整个程序运行期间都是唯一的

java 复制代码
public class Singleton {
    private static Singleton instance = new Singleton();
    private Singleton() {}
    public static Singleton getInstance() {
        return instance;
    }
}

这里的getInstance()必须是静态方法,因为静态方法可以通过类名.方法名的方式调用,而非静态方法则要创建实例,这与我们单例模式的规则不相符。

懒汉模式实现

懒汉模式的特点是需要的时候再创建实例,实例在整个程序运行期间都是唯一的。

java 复制代码
public class Singleton {
    private static Singleton instance = null;
    private Singleton() {}
    public static Singleton getInstance() {
        if (null == instance) {
           instance = new Singleton();
        }
        return instance;
    }
}

上面代码在单线程模式下是没问题的,但是在多线程模式下就会存在线程安全问题。

如果在首次创建实例,多个线程同时调用getInstance()方法,就有可能创建多个实例。

改进1 (创建多个实例)

我们可以进行对 getInstance() 方法进行加锁:

java 复制代码
public class Singleton {
    private static Singleton instance = null;
    private Singleton() {}
    public static synchronized Singleton getInstance() {
        if (null == instance) {
            instance = new Singleton();
        }
        return instance;
    }
}

这样虽然解决刚刚线程安全的问题,但每次调用getInstance()方法都要加锁,增加不少的开销。

我们发现上面线程安全问题只存在于首次创建实例的情况,因此我们只需要对instance为空的时候单独处理即可。

改进2 (性能比较低)

instance为空的时候加锁,再判断一次是否为空即可:

java 复制代码
public class Singleton {
    private static Singleton instance = null;
    private Singleton() {}
    public static Singleton getInstance() {
        if (null == instance) {
            synchronized (Singleton.class) {
                if (null == instance) {
                    instance = new Singleton();
                }
            }
        }
        return instance;
    }
}

粗略的看上去好像没什么问题,实际上这里还有一个指令重排序的问题

通过查阅资料知道instance = new Singleton();这个代码在执行的时候实际是执行分3步骤执行:

java 复制代码
memory = allocate(); //1.分配对象的内存空间
ctorInstance(memory); //2. 初始化对象
instance=memory; //3. 设置instance指向刚分配的内存地址

JVM在执行的时候可能就会优化成 1->3->2的顺序执行

可能导致在多线程环境下,还没执行2就已经被其他线程返回了一个刚分配的地址

同样存在线程安全问题,需要使用volatile关键字来禁止指令重排序

改进3 (volatile禁止指令重排序)

java 复制代码
public class Singleton {
    //volatile 防止指令重排 和可见性
    private static volatile Singleton instance = null;
    private Singleton() {}
    public static Singleton getInstance() {
         //先判断对象是否已经实例化过,没有实例化才进入加锁代码
        if (null == instance) {
            //类对象加锁
            synchronized (Singleton.class) {
                //避免 singleTon== null时,第一个线程实例化后,进入阻塞状态的线程被唤醒后仍会进行实例化。
                if (null == instance) {
                    instance = new Singleton();
                }
            }
        }
        return instance;
    }
}

以上就是线程安全的懒汉模式。

相关推荐
小二·1 分钟前
Java虚拟机(JVM)面试题(51道含答案)
java·开发语言·jvm
无敌最俊朗@4 分钟前
03-事务高频面试总结
java·开发语言·jvm
hygge99911 分钟前
类加载机制、生命周期、类加载器层次、JVM的类加载方式
java·开发语言·jvm·经验分享·面试
一 乐21 分钟前
海产品销售系统|海鲜商城购物|基于SprinBoot+vue的海鲜商城系统(源码+数据库+文档)
java·前端·javascript·数据库·vue.js·后端
mjhcsp1 小时前
C++ 三分查找:在单调与凸函数中高效定位极值的算法
开发语言·c++·算法
我命由我123451 小时前
Element Plus 组件库 - Select 选择器 value 为 index 时的一些问题
开发语言·前端·javascript·vue.js·html·ecmascript·js
沐知全栈开发1 小时前
MySQL 删除数据库指南
开发语言
qq. 28040339842 小时前
js 原型链分析
开发语言·javascript·ecmascript
大飞哥~BigFei2 小时前
RabbitMq消费延迟衰减重试实现思路
java·分布式·rabbitmq
Elnaij2 小时前
从C++开始的编程生活(13)——list和浅谈stack、queue
开发语言·c++