单例模式——懒汉模式的双重检测锁问题

单例模式

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

饿汉模式

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

例如

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

注意:

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

懒汉模式

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

例如

java 复制代码
public class Singleton {
    private static Singleton singleton=null;
    private Singleton() {}

    public static Singleton getSingleton() {
        if (singleton==null) {
            singleton=new Singleton();
        }
        return singleton;
    }
}

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

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

改进1 (创建多个实例)

因此我们可以进行对getSingleton方法进行加锁

java 复制代码
public class Singleton {
    private static Singleton singleton=null;
    private Singleton() {}

    public static synchronized Singleton getSingleton() {
        if (singleton==null) {
            singleton=new Singleton();
        }
        return singleton;
    }
}

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

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

改进2 (性能低)

因此我们可以当singleton为空的时候加锁再判断一次是否为空即可

java 复制代码
public class Singleton {
    private static Singleton singleton=null;
    private Singleton() {}

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

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

通过查阅资料知道singleton=new Singleton();

这个代码在执行的时候实际是执行3句伪代码

memory=allocate(); //1.分配对象的内存空间

ctorInstance(memory); //2. 初始化对象

instance=memory; //3. 设置instance指向刚分配的内存地址

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

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

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

改进3 (指令重排序)

java 复制代码
public class Singleton {
    private static volatile Singleton singleton=null;
    private Singleton() {}

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

这样一个线程安全的懒汉模式就完成了

相关推荐
2401_8576100313 分钟前
Spring Boot框架:电商系统的技术优势
java·spring boot·后端
Dread_lxy26 分钟前
vue 依赖注入(Provide、Inject )和混入(mixins)
前端·javascript·vue.js
希忘auto29 分钟前
详解MySQL安装
java·mysql
冰淇淋烤布蕾40 分钟前
EasyExcel使用
java·开发语言·excel
拾荒的小海螺1 小时前
JAVA:探索 EasyExcel 的技术指南
java·开发语言
Jakarta EE1 小时前
正确使用primefaces的process和update
java·primefaces·jakarta ee
马剑威(威哥爱编程)1 小时前
哇喔!20种单例模式的实现与变异总结
java·开发语言·单例模式
gjh12081 小时前
单例模式和适配器模式的简单介绍
单例模式·适配器模式
奔跑草-1 小时前
【前端】深入浅出 - TypeScript 的详细讲解
前端·javascript·react.js·typescript
java—大象1 小时前
基于java+springboot+layui的流浪动物交流信息平台设计实现
java·开发语言·spring boot·layui·课程设计