【JavaEE】多线程(4) -- 单例模式

目录

什么是设计模式?

1.饿汉模式

2.懒汉模式

线程安全问题


什么是设计模式?

设计模式好⽐象棋中的 "棋谱". 红⽅当头炮, ⿊⽅⻢来跳. 针对红⽅的⼀些⾛法, ⿊⽅应招的时候有⼀ 些固定的套路. 按照套路来⾛局势就不会吃亏.

软件开发中也有很多常⻅的 "问题场景". 针对这些问题场景, ⼤佬们总结出了⼀些固定的套路. 按照这 个套路来实现代码, 也不会吃亏.

单例模式能保证某个类在程序中只存在唯⼀⼀份实例, ⽽不会创建出多个实例. 这⼀点在很多场景上都需要. ⽐如 JDBC 中的 DataSource 实例就只需要⼀个.

单例模式具体的实现⽅式有很多. 最常⻅的是 "饿汉" 和 "懒汉" 两种.

1.饿汉模式

所谓 "饿" 形容 "非常迫切" , 实例实在类加载的时候就创建了, 创建时机非常早, 相当于程序一启动 , 实例就创建了, 就使用 "饿汉" 形容创建实例非常早.

java 复制代码
class Singleton {
    private static Singleton instance = new Singleton();
    
    public static Singleton getInstance() {
        return instance;
    }
    
    private Singleton(){}; //阻止后续代码new出新的实例
}

2.懒汉模式

类加载的时候不创建实例. 第⼀次使⽤的时候才创建实例.

java 复制代码
// 懒汉模式实现单例模式
class SingletonLazy {
    // 这个引用指向唯一的实例, 先初始化为null, 而不是立即创建实例
    private static SingletonLazy instance = null;

    public SingletonLazy getInstance() {
        if(instance == null) {
            instance = new SingletonLazy();
        }

        return instance;
    }

    private SingletonLazy() {};
}

如果是首次调用 getInstance , 那么此时 instance 引用为 null, 就会进入 if 条件, 从而把实例创建出来. 如果是后续再次调用 getInstance , 由于instance 已经不再是 null, 此时不会进入 if , 直接返回之前创建好的引用了.

这样设定, 既可以保证该类的实例是唯一一个, 于此同时, 创建实例的实际就不是程序驱动时了, 而是第一次调用 getInstance 的时候.

线程安全问题

对于饿汉模式来说, getInstance 方法直接返回 Instance 实例, 这个操作本质就是"读操作", 多个线程读取同一个变量, 是线程安全的.

在懒汉模式中, 如下图所示,

有可能会创建出多个实例, 是线程不安全的.

下面是一个改进的代码:

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

这个代码虽然解决的线程不安全的问题, 但是每次执行这串代码的时候都要进行加锁解锁的操作, 这样程序的效率就变差了, 如果在加锁前先进行判断是否需要加锁, 就可以提高程序的效率了.

再次改进后的代码:

java 复制代码
class SingletonLazy {
    private static SingletonLazy instance = null;
    private static Object locker = new Object();
    public static SingletonLazy getInstance() {
        if(instance == null) {
            //如果 instance 为 null, 就说明是首次调用, 首次调用就要考虑线程安全问题, 就要加锁
            //如果是非 null, 就说明是后续调用, 就不用加锁了
            synchronized (locker) {
                if(instance == null) {
                    instance = new SingletonLazy();
                }
            }
        }
        return instance;
    }
    private SingletonLazy(){ };
}

然而, 这段代码还有进一步改进的空间!

解决上述问题核心方法是使用 volatile 关键字

volatile 关键字有两个功能:

  1. 保证内存可见性, 每次访问变量必须要重新读取内存, 而不会优化到寄存器/缓存中
  2. 禁止使用指令重排序. 针对被 volatile 修饰的变量的读写操作相关指令, 是不能被重排序的.

最终修改后的代码:

java 复制代码
class SingletonLazy {
    public volatile SingletonLazy instance = null;
    Object locker = new Object();
    public SingletonLazy getInstance() {
        if(instance == null) {
            synchronized (locker) {
                if(instance == null) {
                    instance = new SingletonLazy();
                }
            }
        }
        return instance;
    }

    private SingletonLazy(){}
}
相关推荐
云水一下4 小时前
从零开始!VMware安装Fedora Workstation 44桌面系统完整教程
前端
wuminyu4 小时前
专家视角看Java字节码加载与存储指令机制
java·linux·c语言·jvm·c++
小码哥_常5 小时前
安卓黑科技:实现多平台商品详情页一键跳转APP
前端
killerbasd5 小时前
还是迷茫 5.3
前端·react.js·前端框架
不会敲代码16 小时前
TCP/IP 与前端性能:从数据包到首次渲染的底层逻辑
前端·tcp/ip
kyriewen6 小时前
奥特曼借GPT-5.5干杯,而你的Copilot正按Token收钱
前端·github·openai
callJJ6 小时前
Spring Data Redis 两种编程模型详解:同步 vs 响应式
java·spring boot·redis·python·spring
AC赳赳老秦6 小时前
投标合规提效:用 OpenClaw 实现标书 / 合同自动审核、关键词校验、格式优化,降低废标风险
开发语言·前端·python·eclipse·emacs·deepseek·openclaw
kyriewen6 小时前
代码写成一锅粥?3个设计模式让你的项目“起死回生”
前端·javascript·设计模式
千寻girling7 小时前
《 Git 详细教程 》
前端·后端·面试