论坛
潜水/灌水快乐,沉淀知识,认识更多同行。
ToB圈子
加入IT圈,遇到更多同好之人。
朋友圈
看朋友圈动态,了解ToB世界。
ToB门户
了解全球最新的ToB事件
博客
Blog
排行榜
Ranklist
文库
业界最专业的IT文库,上传资料也可以赚钱
下载
分享
Share
导读
Guide
相册
Album
记录
Doing
搜索
本版
文章
帖子
ToB圈子
用户
免费入驻
产品入驻
解决方案入驻
公司入驻
案例入驻
登录
·
注册
只需一步,快速开始
账号登录
立即注册
找回密码
用户名
Email
自动登录
找回密码
密码
登录
立即注册
首页
找靠谱产品
找解决方案
找靠谱公司
找案例
找对的人
专家智库
悬赏任务
圈子
SAAS
IT评测·应用市场-qidao123.com技术社区
»
论坛
›
物联网
›
物联网
›
单例模式:懒汉和饿汉
单例模式:懒汉和饿汉
宁睿
论坛元老
|
2025-4-17 06:14:37
|
显示全部楼层
|
阅读模式
楼主
主题
1949
|
帖子
1949
|
积分
5857
目录
一、关于筹划模式
二、单例模式是什么
2.1 饿汉模式
2.2 懒汉模式
三、单例模式和多线程
3.1 饿汉模式
3.2 懒汉模式
一、关于筹划模式
单例模式是一种筹划模式,说它之前先来聊聊筹划模式是什么。
筹划模式,类似于于棋谱(大佬把一些对局整个推演过程写出来)
筹划模式,就相当于步伐员的棋谱。大佬们把一些范例的问题场景,整理出来,而且针对这些场景,代码该怎么写,具体方案给出一些指导和建议。
框架是属于‘硬性要求’,筹划模式是‘软性要求’,目标是一致的。
二、单例模式是什么
单例模式是筹划模式中经典也是比较简单的模式
单个实例(对象)
欺压要求,某个类,在某个步伐中,只有唯逐一个实例(不允许创建多个实例,不允许new多次)
class Test{
}
//对象/实例
Test t = new Test();
复制代码
单例模式,欺压要求一个类不能创建多个对象,通过一些编程技巧,告竣上述的欺压要求。
在代码中,假如创建多个实例,直接编译失败
单例模式两种情况:饿汉模式和懒汉模式。接下来会根据这两种情况举行展开
2.1 饿汉模式
饿,代表着迫切,想要尽早创建实例
class Singleton{
//静态成员的初始化,是在类加载的阶段出发的
//类加载往往就是在程序已启动就会触发
private static Singleton instance = new Singleton();
//后续通过get方法获取这里的实例
public static Singleton getInstance(){
return instance;
}
//单例模式中的“点睛之笔”,在外面进行new操作,都会编译失败
private Singleton() {
}
}
public class demo1 {
public static void main(String[] args) {
Singleton t1 = Singleton.getInstance();
Singleton t2 = Singleton.getInstance();
System.out.println(t1 == t2);
//会报错
//Singleton t3 = new Singleton();
}
}
复制代码
2.2 懒汉模式
懒 和 饿 是相对的,懒是只管晚的创建实例(甚至可能不创建了),延迟创建
懒 在计算机里是褒义词,另一个寄义,是高效率
//懒汉模式
class SingletonLazy{
private static volatile SingletonLazy instance = null;
public static SingletonLazy getInstance() {
if(instance == null){
instance = new SingletonLazy();
}
return instance;
}
private SingletonLazy(){
}
}
public class demo2 {
public static void main(String[] args) {
SingletonLazy t1 = SingletonLazy.getInstance();
SingletonLazy t2 = SingletonLazy.getInstance();
System.out.println(t1 == t2);
//SingletonLazy t3 = new SingletonLazy();
}
}
复制代码
三、单例模式和多线程
上述内容,都是引子,接下来才是正题
上述懒汉/饿汉模式,是否是线程安全?假如不是,该咋办?
这两个版本的getInstance在多线程环境下调用,是否会出bug?
我们可以一个个来看
3.1 饿汉模式
class Singleton{
private static Singleton instance = new Singleton();
public static Singleton getInstance(){
return instance;
}
private Singleton() {
}
}
复制代码
这里只涉及了 return,而return是 读利用,线程安全
String 不可变对象,自然线程安全
3.2 懒汉模式
class SingletonLazy{
private static volatile SingletonLazy instance = null;
public static SingletonLazy getInstance() {
if(instance == null){
instance = new SingletonLazy();
}
return instance;
}
private SingletonLazy(){
}
}
复制代码
if
(
instance
==
null
){
instance
=
new
SingletonLazy();
}
这部分可能会涉及到 多线程 的修改
= 利用是原子的, += -= 这些是非原子的
这里可能会出现bug,这就导致懒汉模式这个写法,getInstance方法是线程不安全的。
怎么办理?
加锁
是一个通例的利用
但也要注意加锁的位置,我们希望的是:
条件和修改都能打包成原子的利用
private static Object locker = new Object();
public static SingletonLazy getInstance() {
synchronized(locker){
if(instance == null){
instance = new SingletonLazy();
}
}
return instance;
}
复制代码
不是写了synchronized,代码就肯定安全,肯定得具体问题具体分析
引入加锁后,后实行的线程就会在加锁位置阻塞,阻塞到前一个线程解锁,当后一个线程进入条件的时候,前一个线程已经修改完毕,Instance不再为null,就不会举行后续new的利用。
也可以举行方法加锁
public synchronized static SingletonLazy getInstance() {
if(instance == null){
instance = new SingletonLazy();
}
return instance;
}
复制代码
但是
加锁引入新的问题:
当把实例创建好之后,后续再调用getInstance,此时都是直接实行return,假如只是举行if判定+return,纯粹的读利用了,读利用不涉及线程安全问题
但是,每次调用上述方法,都会触发一次加锁利用,虽然不涉及线程安全问题,但是多线程情况下,这里的加锁,就会相互阻塞,影响步伐的实行效率
所以我们可以如许:
按需加锁
真正涉及到线程安全的时候,再加锁,不涉及的时候,就不加锁
假如实例已经创建过了,就不涉及线程安全问题提。假如还没创建,就涉及线程安全问题
public static SingletonLazy getInstance() {
if(instance == null){ // 判断是否需要加锁
synchronized (locker){ //
if(instance == null){ // 判断是否需要new对象
instance = new SingletonLazy();
}
}
}
return instance;
}
复制代码
单线程中,连续两个相同的if,是毫无意义的,单线程中,实行流就只有一个,上一个if和下一个if是一样的
但是多线程中,两次判定之间,可能存在其他线程,就把if中的Instance变量给修改了,也就是导致了这里的两次if的结论可能不同
再仔细分析,上述代码,仍然存在问题:
t1线程在读取Instance的时候,t2线程举行修改,是否存在内存可见性问题?
可能存在,编译器优化这件事变,非常复杂
为了稳妥起见,可以给Instance直接加上一个volatile,从根本上杜绝,内存可见性问题
private static volatile SingletonLazy instance = null;
复制代码
这里更关键的问题是:指令重排序
指令重排序也是编译器优化的一种体现形式,编译会在逻辑稳定的前提下,调整代码实行的先后顺序,以到达提升性能的效果
编译器优化,每每不只是javac(Java语言的编译器,Java Compile)通常是javac和jvm配合的效果(甚至是利用系统也要配合)
instance = new SingletonLazy();
复制代码
实例化对象,通常包罗以下三个步骤:
申请内存空间
在空间上构造对象(初始化)
内存空间的首地址,赋值给引用变量
正常来说,这三个步骤,是按照1 2 3 如许的步骤来实行的
但是,在指令重排序下,可能是 1 3 2如许的顺序
单线程下1 2 3还是1 3 2 其实无所谓
假如是1 3 2 如许的顺序实行,多线程下 是会出现bug的
对应的办理方法也要用到 volatile,而且上面也碰巧把这个问题办理了:
private static volatile SingletonLazy instance = null;
复制代码
Volatile 的功能有两方面:
确保每次独缺利用,都是读内存 内存可见性
关于该变量的读取和修改利用,不会触发重排序
免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作!更多信息从访问主页:qidao123.com:ToB企服之家,中国第一个企服评测及商务社交产业平台。
本帖子中包含更多资源
您需要
登录
才可以下载或查看,没有账号?
立即注册
x
回复
使用道具
举报
0 个回复
倒序浏览
返回列表
快速回复
高级模式
B
Color
Image
Link
Quote
Code
Smilies
您需要登录后才可以回帖
登录
or
立即注册
本版积分规则
发表回复
回帖并转播
回帖后跳转到最后一页
发新帖
回复
宁睿
论坛元老
这个人很懒什么都没写!
楼主热帖
java前置学习
【RocketMQ】消息的存储
iOS Widget
简单的用Python对手机号进行加密 ...
【PostgreSQL】PostgreSQL重建与主库不 ...
k8s v-1.20版本部署详细过程[实测可用 ...
基于单片机的压力测控仿真设计(#0024) ...
❤️肝下25万字的《决战Linux到精通》 ...
离线数仓建设,企业大数据的业务驱动与 ...
Unity 将是驱动 C# 增长的引擎吗 ? ...
标签云
AI
运维
CIO
存储
服务器
浏览过的版块
SQL-Server
Mysql
移动端开发
程序人生
云原生
主机安全
图数据库
快速回复
返回顶部
返回列表