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

单例模式

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

饿汉模式

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

例如

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

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

相关推荐
kite01211 小时前
浏览器工作原理06 [#]渲染流程(下):HTML、CSS和JavaScript是如何变成页面的
javascript·css·html
крон1 小时前
【Auto.js例程】华为备忘录导出到其他手机
开发语言·javascript·智能手机
zh_xuan2 小时前
c++ 单例模式
开发语言·c++·单例模式
coderSong25682 小时前
Java高级 |【实验八】springboot 使用Websocket
java·spring boot·后端·websocket
Mr_Air_Boy3 小时前
SpringBoot使用dynamic配置多数据源时使用@Transactional事务在非primary的数据源上遇到的问题
java·spring boot·后端
豆沙沙包?3 小时前
2025年- H77-Lc185--45.跳跃游戏II(贪心)--Java版
java·开发语言·游戏
coding随想4 小时前
JavaScript ES6 解构:优雅提取数据的艺术
前端·javascript·es6
年老体衰按不动键盘4 小时前
快速部署和启动Vue3项目
java·javascript·vue
灵感__idea4 小时前
JavaScript高级程序设计(第5版):无处不在的集合
前端·javascript·程序员
咖啡啡不加糖4 小时前
Redis大key产生、排查与优化实践
java·数据库·redis·后端·缓存