Java【多线程基础4】单例模式中的饿汉模式和懒汉模式

文章介绍了单例模式的概念和重要性,详细讲解了饿汉模式和懒汉模式的实现,并在多线程环境下分析了这两种模式的安全性问题。针对懒汉模式的线程不安全,提出了使用synchronized和volatile进行优化的方法。
摘要由CSDN通过智能技术生成


前言

📕各位读者好, 我是小陈, 这是我的个人主页
📗小陈还在持续努力学习编程, 努力通过博客输出所学知识
📘如果本篇对你有帮助, 烦请点赞关注支持一波, 感激不尽
📙 希望我的专栏能够帮助到你:
JavaSE基础: 基础语法, 类和对象, 封装继承多态, 接口, 综合小练习图书管理系统等
Java数据结构: 顺序表, 链表, 堆, 二叉树, 二叉搜索树, 哈希表等
JavaEE初阶: 多线程, 网络编程, TCP/IP协议, HTTP协议, Tomcat, Servlet, Linux, JVM等(正在持续更新)

上篇[多线程基础3]主要介绍了 : 线程安全问题 , 为什么出现线程不安全以及解决方式, 还有 wait方法 , notify方法 的使用方式
本篇继续介绍多线程相关的基础内容, 内容较多, 分为若干篇持续分享


提示:是正在努力进步的小菜鸟一只,如有大佬发现文章欠佳之处欢迎批评指点~ 废话不多说,直接上干货!

一、什么是单例模式

单例模式是常考的设计模式之一, 能够保证程序中只能存在唯一一份实例, 不能创建多个实例

设计模式可以理解为Java大佬创建好的一套规范, 跟着大佬这么写, 你的代码也不会差

🚗🚗🚗
单例模式的实现方式分为: 饿汉模式和懒汉模式

👉饿汉模式 : 能早点干就早点干, 可以理解为, 每天穿过的袜子脱下来当晚就洗, 主打一个 “着急”
👉懒汉模式 : 能晚点干就晚点干, 可以理解为, 每天穿过的袜子脱下来懒得洗, 等到没有袜子穿的时候再洗, 主打一个 “从容”


二、饿汉模式

在程序中, 饿汉模式的实现就是, 类加载的过程中就创建实例, 类中提供一个获取实例的方式, 返回实例即可

public class Singleton {
	// 成员属性
    private static Singleton singleton = new Singleton();
    // 构造方法
    private Singleton() {}
    // 成员方法, 获取实例
    private static Singleton getSingleton() {
        return singleton;
    }
}

三、懒汉模式

在程序中, 懒汉模式的实现就是, 类加载的时候不创建实例, 要获取这个实例的时候再创建实例

	// 成员属性
    private static Singleton singleton = null;
    // 构造方法
    private Singleton() {}
    public static Singleton getSingleton() {
        if (singleton == null) {
            singleton = new Singleton();
        }
        return singleton;
    }

四、多线程环境下的单例模式

刚刚演示的饿汉模式和懒汉模式, 只是单线程环境下的写法, 请思考 : 如果是多线程环境下, 会不会发生线程不安全呢? (不安全是指:多线程环境下, 创造了不止一个实例, 这就不满足单例模式的定义了)

饿汉模式不会, 懒汉模式会不安全

👉根据上篇文章所介绍的, 懒汉模式中的代码中, 首先会有getSingleton方法 中修改 singleton 的情况, 而 这个 “修改” 操作不满足原子性

比如 : 线程 A 在 getSingleton方法 中判断了 singleton 不为空, 此时被线程 B 抢占执行了 getSingleton方法, 线程 B 中判断 singleton 为空, 因此会 new 一个实例, 再调度回线程 A 中, 继续执行未执行完的代码, 线程 A 中也会 new 一个实例, 如此一来, 整个程序中就创建出了不止一个实例

✅解决这个问题的方式也很简单, 给 “修改” 这个不满足原子性的操作加锁, 让它满足原子性即可

    public static Singleton getSingleton() {
    	// 锁对象是 Singleton 的类对象
        synchronized (Singleton.class) {
            if (singleton == null) {
                singleton = new Singleton();
            }
        }
        return singleton;
    }

加了锁之后, 即便某个线程抢占执行了 Singleton方法, 如果有其他线程正在使用锁, 该该线程就需要阻塞等待其他线程释放锁, 才能获取这个锁


👉这就结束了吗? 还没有

实际上, 这样一来, 任何一个线程每一次调用 getSingleton方法 就需要加锁解锁, 这也是很耗时的操作

经过简单分析代码逻辑就能意识到, 只有当singleton 为空时才需要修改 singleton 的值, 否则直接返回 singleton 即可

✅所以我们可以给刚才的 synchronized 代码块再加一层判断

        if (singleton == null) {
            synchronized (Singleton.class) {
                if (singleton == null) {
                    singleton = new Singleton();
                }
            }
        }

只有 singleton 为空时, 才会加锁解锁, 这一行判断的执行效率, 要比多次的加锁解锁的效率高


这就结束了吗? 还没有

在执行 singleton = new Singleton();这一行代码中, 是有可能出现指令重排序的问题的
new 这个操作其实对应了三条指令
1️⃣ 从内存中申请空间
2️⃣ 调用构造方法
3️⃣ 把对象的引用赋值给变量

正常的指令顺序应该是 1 --> 2 --> 3, 如果发生了指令重排序就会变成 1 --> 3 --> 2, 有可能会造成很不好的影响, 虽然概率很小, 但还是需要尽量规避这种意外情况
✅所以最好使用 volatile 关键字修饰 singleton , 来禁止指令重排序

    volatile private static Singleton singleton = null;

总结

以上就是本篇的全部内容, 主要介绍了饿汉模式和懒汉模式的实现方式, 并且在多线程环境下避免线程安全做出的改进

如果本篇对你有帮助,请点赞收藏支持一下,小手一抖就是对作者莫大的鼓励啦😋😋😋~


上山总比下山辛苦
下篇文章见

评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

灵魂相契的树

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值