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

单例模式

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

饿汉模式

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

例如

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;
    }
}

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

相关推荐
火星思想7 分钟前
Promise 核心知识点(非基础)
前端·javascript·面试
前端大白话8 分钟前
炸裂!10个 React 实战技巧,让你的代码从“青铜”秒变“王者”
前端·javascript·react.js
Tang102414 分钟前
Glide 4.x 版本的图片加载流程
java
10年前端老司机15 分钟前
微信小程序模板语法和事件
前端·javascript·微信小程序
振宇i16 分钟前
Java 富文本转word
java·word·富文本·docx4j
李菠菜16 分钟前
Java对象映射利器MapStruct应用详解与实战指南
java
攻城狮的大师兄16 分钟前
红宝书(第四版)通读之查漏补缺
javascript
神仙别闹19 分钟前
基于Java+MySQL 实现(Web)日程管理系统
java·前端·mysql
黯_森20 分钟前
Java异常机制
java·后端
A阳俊yi44 分钟前
Spring Boot日志配置
java·spring boot·后端