高并发下双重检测锁DCL指令重排问题剖析

news2024/10/5 5:37:11

文章目录

  • 一、引言
    • 1.1 双重检查锁定(Double-Checked Locking,简称DCL)定义介绍
    • 1.2 高并发环境下DCL的应用和优势
  • 二、DCL存在的问题
    • 2.1 DCL的代码示例
    • 2.2 指令重排的定义和工作原理
    • 2.3 指令重排导致DCL失效的情况分析
  • 三、深入分析指令重排和DCL的问题
    • 3.1 示例代码中Singleton对象创建过程的指令重排可能性
    • 3.2 执行顺序的变化导致DCL无法正确工作的剖析
    • 3.3 多线程环境下由于指令重排导致的数据不一致
  • 四、解决方案探究
    • 4.1 volatile关键字的介绍和应用
    • 4.2 利用volatile关键字解决DCL问题的示例和分析
    • 4.3 其他解决DCL问题的方案及其优缺点比较
  • 5. 参考资料

在这里插入图片描述

一、引言

1.1 双重检查锁定(Double-Checked Locking,简称DCL)定义介绍

双重检查锁定(Double-Checked Locking)是一种并发设计模式,该模式减少了同步的开销,提高了执行效率。该模式通过两次检查锁定,确保被检查的代码的线程安全性。在第一次检查中,如果发现变量不满足条件,才进行加锁操作。然后在锁定的区块内再进行一次检查,如果仍不满足条件,才进行相关操作。

1.2 高并发环境下DCL的应用和优势

在高并发环境下,DCL可以显著提高性能。在使用单例模式时,如果没有并发考虑,可能每次访问单例对象时都需要获取同步锁,这会大大影响程序的执行效率。而DCL模式可以避免这个问题,它只在第一次实例化时加锁,之后的访问都不需要获取锁,这大大降低了锁的开销,提高了程序的执行效率。但要注意,由于JVM的指令重排优化,DCL在某些情况下可能会失效,需要慎重使用。

二、DCL存在的问题

2.1 DCL的代码示例

class Singleton {
    private static Singleton instance;
    
    public static Singleton getInstance() {
        if (instance == null) {
            synchronized (Singleton.class) {
                if (instance == null) {
                    instance = new Singleton();
                }
            }
        }
        return instance;
    }
}

这段代码是典型的DCL实现单例模式的例子。在getInstance()方法中,先检查instance是否为null,如果为null,才对Singleton.class对象加锁,然后在锁定区域内再次检查instance是否为null,如果还是null,就创建一个Singleton实例。

2.2 指令重排的定义和工作原理

指令重排是为了提高处理器性能,允许编译器和处理器调整指令的执行顺序。一旦保证最终执行结果与代码顺序执行的结果一致,即使没有按照代码原有的顺序执行也不影响。

2.3 指令重排导致DCL失效的情况分析

在上述DCL代码示例中,instance = new Singleton();这行代码实际上涉及到三个操作:

1)为Singleton分配内存空间;
2)调用Singleton的构造函数,初始化成员字段;
3)将instance对象指向分配的内存空间。

但由于JVM的指令重排优化,执行顺序可能变成1-3-2。也就是说,先为Singleton分配内存空间,然后将instance指向该内存空间,最后调用Singleton的构造函数。在多线程环境下,如果一个线程执行到3,另一个线程刚好执行到第一次检查,发现instance不为null,就直接返回instance,此时得到的Singleton实例其实是未初始化的。这就是JVM的指令重排导致DCL失效的情况。

三、深入分析指令重排和DCL的问题

3.1 示例代码中Singleton对象创建过程的指令重排可能性

在我们的示例代码中,创建Singleton对象的过程,原本的执行顺序是1-2-3,但是由于JVM优化,可能被重新排序为1-3-2。

这种指令的重排,并不是随机的,JVM采用的是"as-if-serial"语义,也就是说,在不改变单线程程序执行结果的前提下,JVM可以对指令进行重新排序。

3.2 执行顺序的变化导致DCL无法正确工作的剖析

由于JVM的指令重排优化,如果执行顺序变为1-3-2,虽然在单线程环境下程序的结果并未改变,但是在多线程环境下,可能导致DCL无法正确工作。

具体来说,当一个线程正在执行到步骤3,也就是将instance指向分配的内存空间,但是还没有执行到步骤2,即初始化Singleton对象。此时,如果另一个线程执行到第一次检查instance是否为null,由于instance已经指向了一个内存空间,所以检查结果不为null,于是直接返回instance。但此时返回的Singleton对象其实还没有被初始化,就会出现问题。

3.3 多线程环境下由于指令重排导致的数据不一致

在多线程环境下,由于指令重排,可能导致数据的不一致。因为指令重排会改变代码的执行顺序,而在多线程环境下,线程之间是并发执行的,对于共享变量的操作顺序,可能会出现预期之外的结果。

例如,在上述例子中,由于指令重排,导致Singleton对象在被一个线程使用前,其实还没有被完全初始化,这就是一个典型的由于指令重排导致的数据不一致的问题。

四、解决方案探究

4.1 volatile关键字的介绍和应用

volatile是Java提供的一种轻量级的同步机制。它有两个主要的特性:保证可见性和禁止指令重排。保证可见性指的是当一个共享变量被volatile修饰时,它会保证修改的值会立即被更新到主存,当有其他线程需要读取时,它会去主存中读取新值。而禁止指令重排则是通过插入内存屏障来实现的。

4.2 利用volatile关键字解决DCL问题的示例和分析

我们可以通过给instance变量添加volatile关键字来解决DCL的问题。代码如下:

public class Singleton {
    private static volatile Singleton instance; 
    private Singleton (){}
    public static Singleton getInstance() {
        if (instance == null) {                         
            synchronized (Singleton.class) {
                if (instance == null) {       
                    instance = new Singleton();
                }
            }
        }
        return instance;
    }
}

在这个实例中,volatile会强制将对instance的写操作刷新到主存,这样当其他线程去读取instance的时候,将总是读取到最新的值。同时,volatile也可以防止JVM对指令进行重新排序,从而避免出现我们之前提到的问题。

4.3 其他解决DCL问题的方案及其优缺点比较

  1. 急切初始化:这种方式是在类加载时就马上创建实例,优点是实现简单,线程安全,但是缺点是如果这个实例很少被使用,那么这种方式就显得有些浪费资源。
  2. 使用静态内部类:利用了Java的类加载机制来保证初始化instance时只有一个线程,这种方式既实现了线程安全,也达到了懒加载的效果,是一种比较推荐的方式。
  3. 使用枚举:这是《Effective Java》作者Josh Bloch 提倡的方式,不仅能避免多线程同步问题,而且还自动支持序列化机制,防止反序列化重新创建新的对象,绝对防止多次实例化,是一种更简洁、高效的方式。

5. 参考资料

  1. 《深入理解Java虚拟机:JVM高级特性与最佳实践》周志明。
  2. 《Effective Java》
  3. java 内存模型

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1041633.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

「大数据-2.1」HDFS集群启停命令

目录 一、HDFS集群一键启停脚本 1. HDFS集群的一键启动脚本 2. HDFS集群的一键关闭脚本 二、单进程启停 1. hadoop-daemon.sh脚本 2. hdfs脚本 三、总结 1. 一键启停脚本 2. 独立进程启停 一、HDFS集群一键启停脚本 Hadoop HDFS组件内置了HDFS集群的一键启停脚本。 1. HDFS集群…

JavaScript 期约 Promise 总结

同步与异步的概念 JavaScript 是一门单线程的语言,这意味着它在任何给定的时间只能执行一个任务。 然而,JavaScript 通过异步编程技术来处理并发操作,以避免阻塞主线程的情况。 在上图中,同步行为的进程 A 因为等待进程 B 执行完…

深入浅出Java的多线程编程——第一篇

目录 1. 认识线程(Thread) 1.1 概念 1.1.1 线程是什么 1.1.2 为啥需要线程 1.1.3 进程和线程的区别 1.1.4 Java的线程和操作系统线程的关系 1.2 第一个多线程程序 1.3 创建线程的方式(5种) 1.3.1 继承Thread类 1.3.2 实现…

电脑开机慢问题的简单处理

电脑用久了,开机时间要10-20分钟特别慢,一下介绍两种简单有效处理方式,这两种方式经测试不会影响原系统软件的使用: 方式一:禁用非必要启动项【效果不是很明显】 利用360里面的优化加速禁用启动项【禁用启动项还有其…

红色模板和黑色模板的区别

红色建筑模板和黑色建筑模板是常见的建筑支模材料,它们在颜色、材料、性能和适用范围等方面存在显著的区别。下面将详细介绍这两种建筑模板的区别。 首先,红色建筑模板通常由胶合板或其他木材制成,外观呈红色,而黑色建筑模板则采用…

MySQL数据库入门到精通6--进阶篇(锁)

5. 锁 5.1 概述 锁是计算机协调多个进程或线程并发访问某一资源的机制。在数据库中,除传统的计算资源(CPU、RAM、I/O)的争用以外,数据也是一种供许多用户共享的资源。如何保证数据并发访问的一致性、有效性是所有数据库必须解决…

沐风老师3DMAX刀剑轨迹拖尾插件SwordTrails使用方法详解

3DMAX刀剑轨迹拖尾插件SwordTrails使用教程 SwordTrail刀剑轨迹拖尾插件,是一款简单的运动轨迹特效工具。 【适用版本】 3dmax2011-2023(不仅于此范围) 【安装方法】 该插件无需安装,使用时直接拖动插件脚本文件到3dmax视口中打…

联机手写汉字识别系统技术要求与测试规程

声明 本文是学习GB-T 18790-2010 联机手写汉字识别系统技术要求与测试规程. 而整理的学习笔记,分享出来希望更多人受益,如果存在侵权请及时联系我们 1 范围 本标准规定了联机手写汉字识别系统的技术要求和测试规程。 本标准适用于微型计算机、手持式信息处理设备等数字化设…

功能定义-后方碰撞预警

功能概述 后方碰撞预警(Rear Collision Warning),简称RCW,其功能表现为实时监测车辆后方环境,并在可能受到后方碰撞危险时发出警告信息 报警区域 其中: L:表示后方盲区,受布置及传感器FOV影响 W&#xff1…

Java实现byte数组与Hex互转

十六进制字符的输出大写字符:0123456789ABCDEF 十六进制字符的输出小写字符:0123456789abcdef下面使用十六进制大写字符。 1、方式1 public class HexStringUtils {private static final char[] HEX_CHAR_TABLE {0, 1, 2, 3, 4, 5, 6, 7, 8, 9, A, B,…

00-MySQL数据库的使用-上

一 数据库基础知识 先谈发音 MySQL如何发音?在国内MySQL发音有很多种,Oracle官方文档说 他们念作 My sequal[si:kwəl]。 数据库基本概念 数据 数据(Data)是指对客观事物进行描述并可以鉴别的符号,这 些符号是可识别…

一篇文章全面解析Modbus协议中的消息帧

在 Modbus网络通信的两种传输模式中( ASCII或RTU),传输设备以将Modbus消息转为有起点和终点的帧,这就允许接收的设备在消息起始处开始工作,读地址分配信息,判断哪一个设备被选中(广播方式则传给…

MySQL 基础

本系列文章为【狂神说 Java 】视频的课堂笔记,若有需要可配套视频学习。 1. 简介 数据库(DB,Database)是安装在操作系统上的存储数据的软件。 关系型数据库(RDB)以行列形式存储数据。 非关系型数据库&am…

如何访问TDH中Inceptor 底层的元数据库TxSQL

如何访问TDH中Inceptor 底层的元数据库TxSQL 1 Inceptor概述 在大数据生态系统中,HIVE是离线数据仓库事实上的标准,绝大多数的大数据分析型系统或数据仓库系统,都是基于HIVE来构建的。 在星环的大数据平台TDH中,在功能上对应开…

PWN环境搭建

虚拟机Ubuntu安装 工具:Vmware 16 以及 Ubuntu 18或20 来源:清华大学开源软件镜像站 | Tsinghua Open Source Mirror 虚拟机安装流程 安装很简单,按照提示一步步来即可 处理器可以多给一些,我给了8个,内核数量不…

CCS介绍

CCS介绍 设置主体颜色 修改字体的颜色和大小 安装一些插件 CCS中的App中心 切换工作空间 导入工程

CarbonData详细解析

一、CarbonData简介 CarbonData是一种新型的Apache Hadoop本地文件格式,使用先进的列式存储、索引、压缩和编码技术,以提高计算效率,有助于加速超过PB数量级的数据查询,可用于更快的交互查询。同时,CarbonData也是一种…

AnyDesk多ID集中控制台V2.0

网盘下载 AnyDesk多ID集中控制台V2.0 软件介绍: 首先大家要知道AnyDesk软件是干嘛的?国外的远程协助工具,和TeamViewer同一个软件,TeamViewer确定需要登录,使用限制5分钟等等缺点,所以自己就用易语言开发An…

uni-app:实现页面效果1

效果 代码 <template><view><view class"add"><image :src"add_icon" mode""></image></view><view class"container_position"><view class"container_info"><view c…

69.渲染函数如何提高Vue应用程序的效率

通过使用虚拟 DOM&#xff0c;Vue 可以比直接操作真实 DOM 更高效地更新和渲染用户界面。渲染函数可用于在服务器上预渲染组件&#xff0c;从而提高应用程序的初始加载性能。渲染函数可让我们完全控制组件的结构和内容&#xff0c;从而构建自定义的复杂用户界面。 h() 函数&…