单例模式下双重校验锁 DCL 的灵魂三问
文章目录
- 前言
- 如何实现一个双重校验锁 DCL
- 定义一个单例变量
- 定义一个获取单例的方法
- 性能优化
- 性能优化带来的一点点问题
- 什么是指令重排?
- 总结
- 如何理解文章开篇理解的三个问题
- 1、为什么需要使用两个 if 语句?
- 2、为什么使用了 synchronized 关键字还需要使用 volatile 关键字?
- 3、双重校验锁使用需要注意的问题
- 个人简介
前言
- hello,大家好,我是 Lorin,今天给大家带来双重校验锁的灵魂三问?以及我们如何一步步实现一个懒汉式单例。开始阅读前,大家可以思考下面三个问题:
DCL 实现中:
1、为什么需要使用两个 if 语句?
2、为什么使用了 synchronized 关键字还需要使用 volatile 关键字?
3、双重校验锁使用需要注意的问题
如何实现一个双重校验锁 DCL
- 双重校验锁 DCL 最常用使用的场景在懒汉式单例,下面我们按照思路简单实现一个懒汉式单例:
定义一个单例变量
public class SingletonDemo {private static Object object = null;
}
定义一个获取单例的方法
- 定义一个单例的获取方法,用于单例的初始化和获取,为了支持多线程访问,我们这里使用 synchronized 进行同步,保证同一时刻只有一个线程访问。
public class SingletonDemo {private static Object object = null;// 初始化和获取实例public Object getObject() {synchronized (SingletonDemo.class) {if (object == null) {object = new Object();}return object;}}
}
性能优化
- 上面的懒汉式单例看起来并没有多大的问题,但是却存在很大的性能的问题,因为我们每次获取我们的实例都需要进行锁的获取和释放,即使我们的实例已经初始化完成,因此为了解决这个问题,我们需要进行一点点优化。
public class SingletonDemo {private volatile static Object object = null;public Object getObject() {// 如果实例已经初始化完成,直接返回实例不获取锁if (object != null){return object;}synchronized (SingletonDemo.class) {if (object == null) {object = new Object();}return object;}}
}
性能优化带来的一点点问题
- 上面的代码表面上看起来已经完美了,解决了并发问题,也优化了性能问题,但是仔细看你会发现了新的问题,由于处理指令重排的优化可能导致 object != null 判断并不准确,怎么理解呢?
创建一个对象分为初始化和实例化两部分,大致可以分为以下几步:1、在堆中申请一份内存
2、创建对象
3、将 object 指向我们对象的内存引用如果没有指令重排的情况下,我们拿到的对象一定是完整的对象,但是可能存在指令重排优化,上面的顺序可能变成下面这样:1、申请一份内存
2、将 object 指向我们对象的内存引用
3、创建对象那么我们将会拿到一个没有实例化完成的对象,因此我们需要禁止指令重排,Java 提供了 volatile 指令来禁止指令重排。
- 题外话:我们写代码的过程其实就是不断在重复优化和解决的问题,直到达到适应我们目前场景、基本情况的最优解(不一定是理论的最优解)。
什么是指令重排?
-
为了提升执行速度/性能,计算机在执行程序代码的时候,会对指令进行重排序。什么是指令重排?简单来说就是系统在执行代码的时候并不一定是按照程序的代码的顺序依次执行。
-
指令重排可以保证单线程串行语义一致(as-if-serial),但是没有义务保证多线程间的语义也一致,所以在多线程下,指令重排可能会导致一些问题。
-
关于指令重排更多内容可以参考 一文读懂 Java Memory Model(JMM)
-
最后,我们得到了终极版本的代码:
public class SingletonDemo {private volatile static Object object = null;public Object getObject() {// 如果实例已经初始化完成,直接返回实例不获取锁if (object != null){return object;}synchronized (SingletonDemo.class) {if (object == null) {object = new Object();}return object;}}
}
总结
如何理解文章开篇理解的三个问题
1、为什么需要使用两个 if 语句?
- 为了性能优化
2、为什么使用了 synchronized 关键字还需要使用 volatile 关键字?
- 性能优化导致带来了多线程指令重排问题,需要使用 volatile 解决指令重排的问题。
3、双重校验锁使用需要注意的问题
- JDK版本大于1.5
- Volatile 屏蔽指令重排序的语义在 JDK1.5 中才被完全修复,此前的 JDK 中即使将变量声明为 volatile 也仍然不能完全避免重排序所导致的问题
- 关于 Volatile 相关介绍可以参考 Volatile 相关章节。
个人简介
👋 你好,我是 Lorin 洛林,一位 Java 后端技术开发者!座右铭:Technology has the power to make the world a better place.
🚀 我对技术的热情是我不断学习和分享的动力。我的博客是一个关于Java生态系统、后端开发和最新技术趋势的地方。
🧠 作为一个 Java 后端技术爱好者,我不仅热衷于探索语言的新特性和技术的深度,还热衷于分享我的见解和最佳实践。我相信知识的分享和社区合作可以帮助我们共同成长。
💡 在我的博客上,你将找到关于Java核心概念、JVM 底层技术、常用框架如Spring和Mybatis 、MySQL等数据库管理、RabbitMQ、Rocketmq等消息中间件、性能优化等内容的深入文章。我也将分享一些编程技巧和解决问题的方法,以帮助你更好地掌握Java编程。
🌐 我鼓励互动和建立社区,因此请留下你的问题、建议或主题请求,让我知道你感兴趣的内容。我期待与你一起在技术之路上前进,一起探讨技术世界的无限可能性。
📖 保持关注我的博客,让我们共同追求技术卓越。
相关文章:
单例模式下双重校验锁 DCL 的灵魂三问
文章目录 前言如何实现一个双重校验锁 DCL定义一个单例变量定义一个获取单例的方法性能优化性能优化带来的一点点问题什么是指令重排? 总结如何理解文章开篇理解的三个问题1、为什么需要使用两个 if 语句?2、为什么使用了 synchronized 关键字还需要使用…...
oracle中关于connect by的语法及实现(前序遍历树)
语法 connect by是是结构化查询中用到的,其基本语法是: 1 select … from tablename 2 start with 条件1 3 connect by 条件2 4 where 条件3; 使用示例 例: create table tree(id int,parentid int); insert into tree values(120,184); …...
学习笔记二十七:K8S控制器Statefulset入门到企业实战应用
这里写目录标题 Statefulset控制器:概念、原理解读Statefulset资源清单文件编写技巧查看定义Statefulset资源需要的字段查看statefulset.spec字段如何定义?查看statefulset的spec.template字段如何定义 Statefulset使用案例:部署web站点State…...
JavaScript 的 闭包
在 JavaScript 中,闭包是一种强大的特性,它允许函数在结束执行后,仍能访问并控制其外部的局部变量。这种特性在许多高级 JavaScript 编程场景中都发挥着关键作用,如创建函数工厂、实现数据隐藏和封装等。 1、闭包的原理 JavaScri…...
二蛋赠书六期:《Linux管理入门经典(第8版)》
前言 大家好!我是二蛋,一个热爱技术、乐于分享的工程师。在过去的几年里,我一直通过各种渠道与大家分享技术知识和经验。我深知,每一位技术人员都对自己的技能提升和职业发展有着热切的期待。因此,我非常感激大家一直…...
19.10 Boost Asio 同步文件传输
在原生套接字编程中我们介绍了利用文件长度来控制文件传输的方法,本节我们将采用另一种传输方式,我们通过判断字符串是否包含goodbye lyshark关键词来验证文件是否传输结束了,当然了这种传输方式明显没有根据长度传输严谨,但使用这…...
微信小程序:两层循环的练习,两层循环显示循环图片大图(大图显示、多层循环)
效果 代码分析 外层循环 外层循环的框架 <view wx:for"{{info}}" wx:key"index"></view> wx:for"{{info}}":这里wx:for指令用于指定要遍历的数据源,即info数组。当遍历开始时,会依次将数组中的每…...
输入几个数,分别输出其中的奇数和偶数
这个问题我们只需要设计几个循环嵌套在一起就可以解决,话不多说,我们直接上代码 目录 1.运行代码 2.运行结果 1.运行代码 #define _CRT_SECURE_NO_WARNINGS 1 #include<stdio.h> #include<string.h>int main() {int arr[10] {1,2,3,4,5,6,…...
香港Web3.0:从政策到实践,探索未来发展路径
随着互联网技术的快速发展,互联网正在经历从Web1.0到Web3.0的重大升级。在这场互联网新技术革命的浪潮中,谁能抓住机遇,谁就能成为未来的引领者。 2022年11月,香港政府发布了《有关香港虚拟资产发展的政策宣言》,彰显…...
Java程序员面试核心知识--Java基础知识(一)
目录 一、Java程序初始化顺序 二、Java的Clone方法作用 三、 OverLoad(重载)与Override(重写)区别 四、abstract class(抽象类)与interface(接口)的异同 五、String、StringBuf…...
Linux的test测试功能
测试文件名的类型,文件是否存在, 文件的权限检测 文件之间的比较 两个整数之间的比较 判断字符串数据 多重条件判定 一个一个来,这个有点多,不过比较有意思,来代码 案例1,判断文件是否存在ÿ…...
为什么看了那么多测试技术帖,感觉自己还是菜?
作为测试新手,最爱莫过于看各大牛发的技术贴,这篇很牛叉,那篇也很有道理,似乎自己看着看着也会成为高手。然而几年后,发现自己对专业知识的理解乱的很,里面更有很多自相矛盾的地方,这到底是哪里…...
HTML和CSS的基础-前端扫盲
想要写出一个网页,就需要学习前端开发(写网页代码)和后端开发(服务器代码)。 对于前端的要求,我们不需要了解很深,仅仅需要做到扫盲的程度就可以了。 写前端,主要用到的有…...
Flutter 02 基础组件 Container、Text、Image、Icon、ListView
一、Container容器组件: demo1: import package:flutter/material.dart;void main() {runApp(MaterialApp(home: Scaffold(appBar: AppBar(title: const Text("你好Flutter")),body: const MyApp(),),)); }// 容器组件 class MyApp extends St…...
[笔记] 字符串输入 #字符输入
字符串的多组输入格式 scanf("%c", &ch)读取单个字符,用EOF作为结束的判断标志。 刷题记录:[题] 查找最大元素 #字符输入 逐个字符手动读取,因为题目的要求,要对每个字符逐个操作,所以就输入的时候顺便…...
服务器数据恢复—EMC存储pool上数据卷被误删的数据恢复案例
服务器数据恢复环境: EMC Unity某型号存储,连接了2台硬盘柜。2台硬盘柜上创建2组互相独立的POOL,2组POOL共有21块520字节硬盘。21块硬盘组建了2组RAID6,1号RAID6有11块硬盘. 2号RAID6有10块硬盘。 服务器故障&检测࿱…...
记录一次@Slf4j log.info 日志信息未输出到日志文件的问题
Spring Boot的起步依赖(如spring-boot-starter-web)中已经包含了Slf4j的依赖,无需额外添加。: <dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-web</artif…...
Git 使用规范流程
开发中使用Git流程 参考文章:阮一峰- Git 使用规范流程 开发新功能:应该新建一个单独的分支(这方面可以参考《Git分支管理策略》)。提交分支commit:分支修改后,就可以提交commit了。提交时,应遵…...
69 内网安全-域横向CobaltStrikeSPNRDP
目录 演示案例:域横向移动RDP传递-Mimikatz域横向移动SPN服务-探针,请求,导出,破解,重写域横向移动测试流程一把梭哈-CobaltStrike初体验 涉及资源 SPN主要是扫描技术,在渗透过程中结合kerberos协议,可以做一些事情 演示案例: 域横向移动RDP传递-Mimik…...
GB28181学习(十四)——语音广播与语音对讲
语音对讲 定义 用户端向设备通过视音频点播请求音频数据;用户端接收音频数据并通过特定的播放设备(如音响)播放;用户端向设备发送广播请求;设备解析广播成功后通过INVITE方法向用户请求音频数据;用户通过音…...
SkyWalking 10.2.0 SWCK 配置过程
SkyWalking 10.2.0 & SWCK 配置过程 skywalking oap-server & ui 使用Docker安装在K8S集群以外,K8S集群中的微服务使用initContainer按命名空间将skywalking-java-agent注入到业务容器中。 SWCK有整套的解决方案,全安装在K8S群集中。 具体可参…...
从深圳崛起的“机器之眼”:赴港乐动机器人的万亿赛道赶考路
进入2025年以来,尽管围绕人形机器人、具身智能等机器人赛道的质疑声不断,但全球市场热度依然高涨,入局者持续增加。 以国内市场为例,天眼查专业版数据显示,截至5月底,我国现存在业、存续状态的机器人相关企…...
1.3 VSCode安装与环境配置
进入网址Visual Studio Code - Code Editing. Redefined下载.deb文件,然后打开终端,进入下载文件夹,键入命令 sudo dpkg -i code_1.100.3-1748872405_amd64.deb 在终端键入命令code即启动vscode 需要安装插件列表 1.Chinese简化 2.ros …...
MySQL 8.0 OCP 英文题库解析(十三)
Oracle 为庆祝 MySQL 30 周年,截止到 2025.07.31 之前。所有人均可以免费考取原价245美元的MySQL OCP 认证。 从今天开始,将英文题库免费公布出来,并进行解析,帮助大家在一个月之内轻松通过OCP认证。 本期公布试题111~120 试题1…...
【碎碎念】宝可梦 Mesh GO : 基于MESH网络的口袋妖怪 宝可梦GO游戏自组网系统
目录 游戏说明《宝可梦 Mesh GO》 —— 局域宝可梦探索Pokmon GO 类游戏核心理念应用场景Mesh 特性 宝可梦玩法融合设计游戏构想要素1. 地图探索(基于物理空间 广播范围)2. 野生宝可梦生成与广播3. 对战系统4. 道具与通信5. 延伸玩法 安全性设计 技术选…...
【生成模型】视频生成论文调研
工作清单 上游应用方向:控制、速度、时长、高动态、多主体驱动 类型工作基础模型WAN / WAN-VACE / HunyuanVideo控制条件轨迹控制ATI~镜头控制ReCamMaster~多主体驱动Phantom~音频驱动Let Them Talk: Audio-Driven Multi-Person Conversational Video Generation速…...
路由基础-路由表
本篇将会向读者介绍路由的基本概念。 前言 在一个典型的数据通信网络中,往往存在多个不同的IP网段,数据在不同的IP网段之间交互是需要借助三层设备的,这些设备具备路由能力,能够实现数据的跨网段转发。 路由是数据通信网络中最基…...
大模型——基于Docker+DeepSeek+Dify :搭建企业级本地私有化知识库超详细教程
基于Docker+DeepSeek+Dify :搭建企业级本地私有化知识库超详细教程 下载安装Docker Docker官网:https://www.docker.com/ 自定义Docker安装路径 Docker默认安装在C盘,大小大概2.9G,做这行最忌讳的就是安装软件全装C盘,所以我调整了下安装路径。 新建安装目录:E:\MyS…...
【记录坑点问题】IDEA运行:maven-resources-production:XX: OOM: Java heap space
问题:IDEA出现maven-resources-production:operation-service: java.lang.OutOfMemoryError: Java heap space 解决方案:将编译的堆内存增加一点 位置:设置setting-》构建菜单build-》编译器Complier...
【阅读笔记】MemOS: 大语言模型内存增强生成操作系统
核心速览 研究背景 研究问题:这篇文章要解决的问题是当前大型语言模型(LLMs)在处理内存方面的局限性。LLMs虽然在语言感知和生成方面表现出色,但缺乏统一的、结构化的内存架构。现有的方法如检索增强生成(RA…...
