单例模式下双重校验锁 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方法向用户请求音频数据;用户通过音…...
浏览器访问 AWS ECS 上部署的 Docker 容器(监听 80 端口)
✅ 一、ECS 服务配置 Dockerfile 确保监听 80 端口 EXPOSE 80 CMD ["nginx", "-g", "daemon off;"]或 EXPOSE 80 CMD ["python3", "-m", "http.server", "80"]任务定义(Task Definition&…...
Python爬虫实战:研究MechanicalSoup库相关技术
一、MechanicalSoup 库概述 1.1 库简介 MechanicalSoup 是一个 Python 库,专为自动化交互网站而设计。它结合了 requests 的 HTTP 请求能力和 BeautifulSoup 的 HTML 解析能力,提供了直观的 API,让我们可以像人类用户一样浏览网页、填写表单和提交请求。 1.2 主要功能特点…...
装饰模式(Decorator Pattern)重构java邮件发奖系统实战
前言 现在我们有个如下的需求,设计一个邮件发奖的小系统, 需求 1.数据验证 → 2. 敏感信息加密 → 3. 日志记录 → 4. 实际发送邮件 装饰器模式(Decorator Pattern)允许向一个现有的对象添加新的功能,同时又不改变其…...
【位运算】消失的两个数字(hard)
消失的两个数字(hard) 题⽬描述:解法(位运算):Java 算法代码:更简便代码 题⽬链接:⾯试题 17.19. 消失的两个数字 题⽬描述: 给定⼀个数组,包含从 1 到 N 所有…...
FastAPI 教程:从入门到实践
FastAPI 是一个现代、快速(高性能)的 Web 框架,用于构建 API,支持 Python 3.6。它基于标准 Python 类型提示,易于学习且功能强大。以下是一个完整的 FastAPI 入门教程,涵盖从环境搭建到创建并运行一个简单的…...
Qwen3-Embedding-0.6B深度解析:多语言语义检索的轻量级利器
第一章 引言:语义表示的新时代挑战与Qwen3的破局之路 1.1 文本嵌入的核心价值与技术演进 在人工智能领域,文本嵌入技术如同连接自然语言与机器理解的“神经突触”——它将人类语言转化为计算机可计算的语义向量,支撑着搜索引擎、推荐系统、…...
srs linux
下载编译运行 git clone https:///ossrs/srs.git ./configure --h265on make 编译完成后即可启动SRS # 启动 ./objs/srs -c conf/srs.conf # 查看日志 tail -n 30 -f ./objs/srs.log 开放端口 默认RTMP接收推流端口是1935,SRS管理页面端口是8080,可…...
苍穹外卖--缓存菜品
1.问题说明 用户端小程序展示的菜品数据都是通过查询数据库获得,如果用户端访问量比较大,数据库访问压力随之增大 2.实现思路 通过Redis来缓存菜品数据,减少数据库查询操作。 缓存逻辑分析: ①每个分类下的菜品保持一份缓存数据…...
Java-41 深入浅出 Spring - 声明式事务的支持 事务配置 XML模式 XML+注解模式
点一下关注吧!!!非常感谢!!持续更新!!! 🚀 AI篇持续更新中!(长期更新) 目前2025年06月05日更新到: AI炼丹日志-28 - Aud…...
IoT/HCIP实验-3/LiteOS操作系统内核实验(任务、内存、信号量、CMSIS..)
文章目录 概述HelloWorld 工程C/C配置编译器主配置Makefile脚本烧录器主配置运行结果程序调用栈 任务管理实验实验结果osal 系统适配层osal_task_create 其他实验实验源码内存管理实验互斥锁实验信号量实验 CMISIS接口实验还是得JlINKCMSIS 简介LiteOS->CMSIS任务间消息交互…...
