单例模式、指令重排序、锁、有序性

这篇具有很好参考价值的文章主要介绍了单例模式、指令重排序、锁、有序性。希望对大家有所帮助。如果存在错误或未考虑完全的地方,请大家不吝赐教,您也可以点击"举报违法"按钮提交疑问。

今天在回顾单例模式时,我们都知道懒汉式单例中有一种叫做双重检查锁的单例模式。

我们来看下下面的代码有没有问题:
单例模式、指令重排序、锁、有序性,单例模式
这段代码我们可以看到,即优化了性能,在多线程情况下,如果实例不为空了,则直接返回了。这样就不用等待排队获取锁了。
同时也保证了线程的安全性,即全程只会出现一个实例。

但是真的没有问题了吗?我们来分析一下:
在执行到 lazyDoubleCheckSingleton = new LazyDoubleCheckSingleton();这条语句时,这里是会发生一个指令重排序的问题的。什么是指令重排序呢?
正常的我们创建一个对象,对于底层来说是: a. 内存分配 b. 初始化 c. 返回对象引用

但是由于指令重排序:我们的指令执行过程可能会成为 a. 内存分配 b. 返回对象引用 c. 初始化

如果是我们指令重排序的这种结果。那我们上面的代码就会产生问题了。
即:假设第一个线程执行到了 lazyDoubleCheckSingleton = new LazyDoubleCheckSingleton(); 这条语句,但是只是执行了指令中的 a 、 b 即初始化还没有完成,但此时 lazyDoubleCheckSingleton这个引用已经不为空了,此时第二个线程过来,在外层判断发现lazyDoubleCheckSingleton不等于空,就直接返回了,但此时返回的对象很明显是个半成品,还没有初始化。因此就会导致产生不可预估的错误。

具体为什么会产生指令重排序,或者指令重排序的详细概念请看https://blog.csdn.net/weixin_37841366/article/details/113086438

以上是问题的背景。

但是我的疑问是为什么会产生指令重排序呢?我印象中学习过的JUC不是说加锁:即Synchronized是可以保证 有序性、可见性、原子性的吗?这个赋值创建的语句这不是在Synchronized代码块里面呢吗?怎么会有指令重排序的问题呢?

下面我们就来分析一下为什么会产生这样的问题吧~

首先我们需要先好好理解一下加锁保证的有序性和volatile关键字防止指令重排序保证的有序性的区别。

首先我们需要明确一点:那就是加锁是无法防止指令重排序的。那为什么说他能够保证有序性呢?

我们需要了解一个语义:

编译器和处理器必须遵守as-if-serial语义,即不管怎么重排序(编译器和处理器为了提高并行度),单线程程序的执行结果不能被改变。

因为Synchronized块中的代码相当于是单线程执行的,而因为这个语义的存在,单线程执行的执行结果是保证不能被改变的,因此Synchronized代码块包裹的代码是有序的代码。这里的有序指的是宏观的有序。

但我们的双检查单例为什么靠Synchronized锁做不到保证有序呢?
因为我们在代码块外面的那个if判断是不受Synchronized控制的。Synchronized的内部是有序了。但是外部依旧无序。

因此上面的代码我们需要添加volatile关键字防止指令重排序。让其保证微观上的强有序性。文章来源地址https://www.toymoban.com/news/detail-567770.html

到了这里,关于单例模式、指令重排序、锁、有序性的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处: 如若内容造成侵权/违法违规/事实不符,请点击违法举报进行投诉反馈,一经查实,立即删除!

领支付宝红包 赞助服务器费用

相关文章

  • JUC之可见性和有序性

    目录 java内存模型 可见性 现象出现  现象解释  解决方法 有序性 诡异的结果 解决方法 Happens-before规则 Java内存模型(Java Memory Model,简称JMM)定义了Java程序中各种变量、对象的访问方式和内存关系。 JMM规定了线程之间的可见性、原子性、顺序性等问题, 确保多线程并发访

    2024年02月09日
    浏览(36)
  • 设计模式学习(一)单例模式补充——指令重排

    目录 前言 指令重排简介 指令重排对单例模式的影响 改进方法 std::call_once和std::once_flag std::atomic和内存顺序 局部静态变量 总结 参考文章 在《单例模式学习》中曾提到懒汉式DCLP的单例模式实际也不是线程安全的,这是编译器的指令重排导致的,本文就简单讨论一下指令重排

    2024年03月19日
    浏览(43)
  • 【JavaEE】并发编程(多线程)线程安全问题&内存可见性&指令重排序

    目录 第一个问题:什么是线程安全问题? 第二个问题:为什么会出现线程安全问题?  第三个问题:如何解决多线程安全问题?  第四个问题:产生线程不安全的原因有哪些?  第五个问题:内存可见性问题及解决方案  第六个问题:指令重排序问题? 线程安全就是多线程

    2024年02月01日
    浏览(62)
  • 面试官:synchronized 能不能禁止指令重排序?大部分人都会答错!

    首先一定要明确:指令重排序和有序性是不一样的。这一点非常重要。 我们经常都会这么说: volatile能保证内存可见性、禁止指令重排序但是不能保证原子性。 synchronized能保证原子性、可见性和有序性。 注意:这里的有序性并不是代表能禁止指令重排序。 举个例子: 在双

    2024年02月11日
    浏览(36)
  • JavaEE 初阶篇-深入了解多线程安全问题(指令重排序、解决内存可见性与等待通知机制)

    🔥博客主页: 【 小扳_-CSDN博客】 ❤感谢大家点赞👍收藏⭐评论✍ 文章目录         1.0 指令重排序概述         1.1 指令重排序主要分为两种类型         1.2 指令重排序所引发的问题         2.0 内存可见性概述         2.1 导致内存可见性问题主要涉及两个方面      

    2024年04月15日
    浏览(45)
  • 【单例模式】饿汉模式和懒汉模式的单例模式

    设计模式是一种 在软件设计中经过验证的解决问题的方案或者模版 。它们是从实践中总结出来的,可以帮助解决常见的设计问题,提高代码的重用性、维护性和扩展性。 设计模式可以分为三大类: 创建型模式(Creational Patterns) :创建型模式关注对象的实例化过程,包括如

    2024年02月16日
    浏览(66)
  • 设计模式学习(一)单例模式补充——单例模式析构

    目录 前言 无法调用析构函数的原因 改进方法 内嵌回收类 智能指针 局部静态变量 参考文章 在《单例模式学习》中提到了,在单例对象是通过 new 动态分配在堆上的情况下,当程序退出时,不会通过C++的RAII机制自动调用其析构函数。本文讨论一下这种现象的原因以及

    2024年03月19日
    浏览(50)
  • Java单例模式详解--七种单例模式实现+单例安全+实际应用场景

    保证了一个类只有一个实例,并且提供了一个全局访问点。单例模式的主要作用是节省公共资源,方便控制,避免多个实例造成的问题。 实现单例模式的三点: 私有构造函数 私有静态变量维护对象实例 公有静态方法提供获取实例对象 七种单例模式实现 1.静态类:第一次运

    2024年02月04日
    浏览(59)
  • 【地铁上的设计模式】--创建型模式:单例模式(五)--枚举单例

    什么是枚举单例 枚举单例是指使用枚举类型来实现单例模式,它是单例模式中最简单、最安全的一种实现方式。在枚举类型中定义的枚举值只会被实例化一次,即保证了全局唯一的实例,而且实现简单、线程安全、防止反射攻击、支持序列化等。 如何实现枚举单例 实现枚举

    2023年04月25日
    浏览(78)
  • JavaEE 初阶篇-深入了解单例模式(经典单例模式:饿汉模式、懒汉模式)

    🔥博客主页: 【 小扳_-CSDN博客】 ❤感谢大家点赞👍收藏⭐评论✍ 文章目录         1.0 单例模式的概述         2.0 单例模式 - 饿汉式单例         2.1 关于饿汉式单例的线程安全问题         3.0 单例模式 - 懒汉式单例         3.1 关于懒汉式单例的线程安全问题      

    2024年04月15日
    浏览(43)

觉得文章有用就打赏一下文章作者

支付宝扫一扫打赏

博客赞助

微信扫一扫打赏

请作者喝杯咖啡吧~博客赞助

支付宝扫一扫领取红包,优惠每天领

二维码1

领取红包

二维码2

领红包