单例模式之饿汉模式&懒汉模式

简介: 单例模式之饿汉模式&懒汉模式

a07cc3862c2748cfa6c92266f0164c51.png

前言

单例模式能保证某个类在程序中只存在唯一一份实例,而不会创建出多个实例,比如 JDBC 中的 DataSource 实例就只需要一个。单例模式具体的实现方式有"饿汉" 和 "懒汉" 两种。

1.饿汉模式(类加载的同时创建实例)

class Singleton {
    // 先创建出示例
    private static Singleton instance = new Singleton();
    // 如果需要使用这个唯一实例, 统一通过 Singleton.getInstance() 方式来获取.
    public static Singleton getInstance() {
        return instance;
    }
    // 把构造方法设为 private. 在类外面, 就无法通过 new 的方式来创建这个 Singleton 实例
    private Singleton() {}
}

此时饿汉模式即使在多线程情况下,也是线程安全的,因为只涉及到读操作。

2.懒汉模式

2.1单线程版的懒汉模式

class SingletonLazy {
    private static SingletonLazy instance = null;
    public static SingletonLazy getInstance() {
                if (instance == null) {
                    instance = new SingletonLazy();
                }
        return instance;
    }
    private SingletonLazy() {}
}

如果此时这种懒汉模式放在多线程中是不安全的,因为既涉及到了读操作也涉及到了写操作。如果此时多个线程同时都读到了instance == null,那么就会多次进行new操作,这显然就不是单例了。

2.2进行加锁,对单线程版懒汉模式进行改进

class SingletonLazy {
    private static SingletonLazy instance = null;
    public static SingletonLazy getInstance() {
        synchronized (SingletonLazy.class){
            if (instance == null) {
                instance = new SingletonLazy();
            }
        }
        return instance;
    }
    private SingletonLazy() {}
}

这样进行加锁之后,就保证了读操作和修改操作是一个整体了。但是,目前进行加锁改进后的懒汉模式代码仍然还有问题,这样的加锁之后,就意味着每一次的getInstance都需要加锁,因为进行加锁操作是需要花费很大开销的,那么每一次都需要进行加锁,会浪费很大的开销。这里的加锁的本意是只在new出对象之前加,是有必要的,一旦new对象完成,后续加锁也就没有意义了,因为instance的值一定是非空的,直接就走return了。所以,还有改进的空间。

2.3继续改进(满足特定条件才进行加锁,也就是第一次new对象的时候才进行加锁)

class SingletonLazy {
    private static SingletonLazy instance = null;
    public static SingletonLazy getInstance() {
        if (instance == null){
            synchronized (SingletonLazy.class){
                if (instance == null) {
                    instance = new SingletonLazy();
                }
            }
        }
        return instance;
    }
    private SingletonLazy() {}
}

第一层if (instance == null)判断是为了判断是否是第一次new对象,是否需要进行加锁;synchronized (SingletonLazy.class)此处就是在满足了特定条件后才进行加锁,即第一次new对象的时候;第二层 if (instance == null)是判断是否要创建对象。加锁操作可能会引起线程阻塞,当执行到锁结束之后,执行到第二个if 的时候第二个if和第一个if之间可能已经隔了很久的时间,程序的运行内部的状态,这些变量的值,都可能已经发生很大改变了,比如第一次都是null,有很多个线程都进入了第一个if,但是此时在synchronized这里阻塞等待,只有一个能进去,当后面的线程再次进去的时候,一定要靠第二个if来判断,因为此时instance很可能已经被第一次进去的线程给改了,所以很可能不为空,所以还需要第二个if来判断

但是此时,这段代码还有一些问题,那就是内存可见性问题。比如,同时有很多很多的线程都去getInstance,此时只有一个线程是真正进入了第二个if里面读取了内存,其他在外面的很多线程都是读寄存器/cache,那么此时编译器很可能会认为多次读取到的都是null,有可能会被优化处理。

还有就是在这个过程中,会涉及到指令重排序的问题。此处的instance = new SingletonLazy();可以拆分成三个步骤,(1)申请内存空间,(2)调用构造方法,把这个内存空间初始化成一个合理的对象,(3)把内存空间的地址赋值给instance引用。

在正常情况下,是按照123这个顺序来执行的,但是编译器为了提高程序效率,可能会调整代码的执行顺序,比如把123变成132。

假设t1是按照132的步骤来执行的,当t1执行到13步骤结束,2步骤之前的时候,被切出CPU了,换t2来执行。此时,t1执行完步骤3之后,在t2看来,此处的引用就非空了,此时此刻,t2就直接return了instance引用,并且可能会尝试使用引用中的属性。但是由于t1中的步骤2还没有完成,t2拿到的是非法的对象,还没有构造完成的不完整的对象。


2.4使用volatile关键字来解决内存可见性和禁止指令重排序的问题

class SingletonLazy {
    private volatile static SingletonLazy instance = null;
    public static SingletonLazy getInstance() {
        if (instance == null){
            synchronized (SingletonLazy.class){
                if (instance == null) {
                    instance = new SingletonLazy();
                }
            }
        }
        return instance;
    }
    private SingletonLazy() {}
}



相关文章
|
12天前
|
弹性计算 关系型数据库 微服务
基于 Docker 与 Kubernetes(K3s)的微服务:阿里云生产环境扩容实践
在微服务架构中,如何实现“稳定扩容”与“成本可控”是企业面临的核心挑战。本文结合 Python FastAPI 微服务实战,详解如何基于阿里云基础设施,利用 Docker 封装服务、K3s 实现容器编排,构建生产级微服务架构。内容涵盖容器构建、集群部署、自动扩缩容、可观测性等关键环节,适配阿里云资源特性与服务生态,助力企业打造低成本、高可靠、易扩展的微服务解决方案。
1264 5
|
1天前
|
存储 关系型数据库 分布式数据库
PostgreSQL 18 发布,快来 PolarDB 尝鲜!
PostgreSQL 18 发布,PolarDB for PostgreSQL 全面兼容。新版本支持异步I/O、UUIDv7、虚拟生成列、逻辑复制增强及OAuth认证,显著提升性能与安全。PolarDB-PG 18 支持存算分离架构,融合海量弹性存储与极致计算性能,搭配丰富插件生态,为企业提供高效、稳定、灵活的云数据库解决方案,助力企业数字化转型如虎添翼!
|
11天前
|
机器学习/深度学习 人工智能 前端开发
通义DeepResearch全面开源!同步分享可落地的高阶Agent构建方法论
通义研究团队开源发布通义 DeepResearch —— 首个在性能上可与 OpenAI DeepResearch 相媲美、并在多项权威基准测试中取得领先表现的全开源 Web Agent。
1281 87
|
12天前
|
云栖大会
阿里云云栖大会2025年9月24日开启,免费申请大会门票,速度领取~
2025云栖大会将于9月24-26日举行,官网免费预约畅享票,审核后短信通知,持证件入场
1823 13