前言
📕各位读者好, 我是小陈, 这是我的个人主页
📗小陈还在持续努力学习编程, 努力通过博客输出所学知识
📘如果本篇对你有帮助, 烦请点赞关注支持一波, 感激不尽
📙 希望我的专栏能够帮助到你:
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;
总结
以上就是本篇的全部内容, 主要介绍了饿汉模式和懒汉模式的实现方式, 并且在多线程环境下避免线程安全做出的改进
如果本篇对你有帮助,请点赞收藏支持一下,小手一抖就是对作者莫大的鼓励啦😋😋😋~
上山总比下山辛苦
下篇文章见