使用双重检查锁定实现单例时,我们是否需要 易失性

2023-11-29

假设我们使用双重检查锁来实现单例模式:

    private static Singleton instance;

    private static Object lock = new Object();

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

我们需要将变量“实例”设置为“易失性”吗?我听到一种说法是我们需要它来禁用重新排序:

创建对象时,可能会发生重新排序:

address=alloc
instance=someAddress
init(someAddress)

他们说,如果最后两个步骤被重新排序,我们需要一个易失性实例来禁用重新排序,否则其他线程可能会得到一个未完全初始化的对象。

然而,由于我们处于同步代码块中,我们真的需要 volatile 吗?或者一般来说,我可以说同步块可以保证共享变量对其他线程是透明的,并且即使它不是 volatile 变量也不会重新排序吗?


在我进行解释之前,您需要了解编译器所做的一项优化(我的解释非常简单)。假设您的代码中的某处有这样的序列:

 int x = a;
 int y = a;

编译器将它们重新排序为完全有效:

 // reverse the order
 int y = a;
 int x = a;

No one writes to a这里,只有两个reads of a,因此允许这种类型的重新排序。

一个稍微复杂一点的例子是:

// someone, somehow sets this
int a;

public int test() {

    int x = a;

    if(x == 4) {
       int y = a;
       return y;
    }

    int z = a;
    return z;
}

编译器可能会查看此代码并注意到如果输入了此代码if(x == 4) { ... }, 这 :int z = a;永远不会发生。但是,同时,你可以稍微不同地思考一下:如果if statement输入后,我们不在乎int z = a;无论执行与否,都不会改变以下事实:

 int y = a;
 return y;

仍然会发生。因此,让我们这样做int z = a;渴望:

public int test() {

   int x = a;
   int z = a; // < --- this jumped in here

   if(x == 4) {
       int y = a;
       return y;
   }

   return z;
}

现在编译器可以进一步重新排序:

// < --- these two have switched places
int z = a;
int x = a;

if(x == 4) { ... }    

有了这些知识,我们现在就可以尝试了解正在发生的事情。

让我们看看你的例子:

 private static Singleton instance; // non-volatile     

 public static Singleton getInstance() {
    if (instance == null) {  // < --- read (1)
        synchronized (lock) {
            if (instance == null) { // < --- read (2)
                instance = new Singleton(); // < --- write 
            }
        }
    }
    return instance; // < --- read (3)
}

有 3 次阅读instance(也叫load)和一个单一的write到它(也称为store)。听起来可能很奇怪,但如果read (1)见过一个instance那不为空(意味着if (instance == null) { ... }未输入),这并不意味着read (3)将返回一个非空实例,它完全有效read (3)仍然返回null。这应该会融化你的大脑(我的也有好几次)。幸运的是,有一种方法可以证明这一点。

编译器可能会向您的代码添加这样一个小的优化:

public static Singleton getInstance() {
    if (instance == null) {
        synchronized (lock) {
            if (instance == null) {
                instance = new Singleton();
                // < --- we added this
                return instance;
            }
        }
    }
    return instance;
}

它插入了一个return instance,从语义上讲,这不会以任何方式改变代码的逻辑。

那么,有一个一定的优化编译器这样做会对我们有所帮助。我不会深入讨论细节,但它引入了一些本地字段(好处在于该链接)来执行所有读取和写入(存储和加载)。

public static Singleton getInstance() {
    Singleton local1 = instance;   // < --- read (1)
    if (local1 == null) {
        synchronized (lock) {
            Singleton local2 = instance; // < --- read (2)
            if (local2 == null) {
                Singleton local3 = new Singleton();
                instance = local3; // < --- write (1)
                return local3;
            }
        }
    }

    Singleton local4 = instance; // < --- read (3)
    return local4;
}

现在编译器可能会看到这个并看到:如果if (local2 == null) { ... }已输入,Singleton local4 = instance;永远不会发生(或者正如我开始这个答案的例子中所说:如果Singleton local4 = instance;发生)。但为了进入if (local2 == null) {...},我们需要输入这个if (local1 == null) { ... }第一的。现在让我们从整体上推理一下:

if (local1 == null) { ... } NOT ENTERED => NEED to do : Singleton local4 = instance

if (local1 == null) { ... } ENTERED && if (local2 == null) { ... } NOT ENTERED 
=> MUST DO : Singleton local4 = instance. 

if (local1 == null) { ... } ENTERED && if (local2 == null) { ... } ENTERED
=> CAN DO : Singleton local4 = instance.  (remember it does not matter if I do it or not)

您可以看到,在所有情况下,这样做都没有坏处:Singleton local4 = instance 在任何 if 检查之前.

经过所有这些疯狂之后,你的代码可能会变成:

 public static Singleton getInstance() {

    Singleton local4 = instance; // < --- read (3)
    Singleton local1 = instance;   // < --- read (1)

    if (local1 == null) {
        synchronized (lock) {
            Singleton local2 = instance; // < --- read (2)
            if (local2 == null) {
                Singleton local3 = new Singleton();
                instance = local3; // < --- write (1)
                return local3;
            }
        }
    }

    return local4;
}

有两个独立的读取instance here:

Singleton local4 = instance; // < --- read (3)
Singleton local1 = instance;   // < --- read (1)

if(local1 == null) {
   ....
}

return local4;

你读instance into local4(我们假设一个null),然后你读到instance into local1(假设某个线程已经将其更改为非空)并且...您的getInstance将返回一个null, not a Singleton. q.e.d.


结论:这些优化是only可能的时候private static Singleton instance; is non-volatile,否则很多优化都会被禁止,这样的事情根本不可能发生。所以,是的,使用volatile是此模式正常工作的必须条件。

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

使用双重检查锁定实现单例时,我们是否需要 易失性 的相关文章

  • 中断并标签,“标签 MyLabel 丢失”

    我有这样的代码 if condition1 break MyLabel while true some code here MyLabel if condition2 break more code here 我收到此错误 标签 MyLab
  • 在 spring-boot 中设置 base-href 和 context-path - Angular 6 应用程序

    我正在构建一个 Web 应用程序 其中 UI 客户端组件是使用 Angular 6 构建的 后端 服务器端 位于 Spring boot 上 申请预计可在 http localhost 8080 FUtility 我通过在 appmodul
  • Java Swing 应用程序消息对话框帮助

    我正在开发 Java Swing 应用程序 我需要创建一个如图所示的对话框 我不知道这个的名字 我无法解释 所以我附上一张照片 请告诉我这叫什么以及如何在我的 GUI 应用程序中创建它 给猫剥皮的方法不止一种 public final cl
  • 我在使用 JavaFX 绘制十字时遇到问题

    我正在尝试编写代码 在网格上对角绘制 3 个形状 前两个形状是正方形和圆形 我能做到 然而 第三种形状让我有些悲伤 我应该画一个十字 T 版本 而不是 X 每次我写出代码时 它看起来就像一个侧面 我知道我只是错过了一些简单的东西 但我真的很
  • 始终等待页面加载到 PageObjects 上

    因此 当出现问题时 我只是创建了一个简单的 selenium JBehave 代码 我将首先发布简化的代码 然后稍后解释我的问题是什么 所以这里我们有一个简单的 AbstractClass 它将在我的 PageObjects 上继承 此类仅
  • Android:TelephonyManager 类

    我不明白为什么 API 文档中这么写TelephonyManager类是public 但是当我尝试创建一个实例时 它说它不是公共类 并且无法从包中访问 我看到它也说使用Context getSystemService Context TEL
  • Eclipse 无法识别 persistence.xml 的内容

    我在 eclipse 中收到以下错误 persistence xml 文件没有可识别的内容 我的 persistence xml 文件在我的应用程序中工作得很好 但 eclipse 一直给我这个错误 我在移动文件并使用 m2eclipse
  • Eclipse Oxygen - 该项目未构建,因为其构建路径不完整

    我刚刚安装了 Eclipse Oxygen 并尝试在工作台中打开现有项目 但收到此错误 该项目未构建 因为其构建路径不完整 不能 找到 java lang Object 的类文件 修复构建路径然后尝试 建设这个项目 我尝试右键单击该项目 转
  • 合并和颜色样式不适用于 Apache POI excel 2003 格式

    在 Apache POI 中 我为某些单元格应用了一些样式并合并了这些单元格 当我在 2010 年或 2007 年打开时 它工作正常 但在 2003 年 格式样式消失了 每次保存 2003 Excel 文件之前都会弹出兼容性检查对话框 请参
  • 我可以使用 Selenium Webdriver 测试元素的顺序吗?

    有一个表单 其中有 3 个字段 具有 3 个不同的 ID fieldset div div fieldset
  • 将 Maven 控制台与 m2eclipse 一起使用

    Maven 新手在这里 有没有办法在 Eclipse 中打开控制台并在 M2Eclipse 插件上执行 Maven 命令 这是一个非常好的插件 但我环顾四周 没有找到我想要的一些功能 谢谢 如果你想运行特定的maven插件 你可以这样做 g
  • Log4j 2.0 中发现 ClassNotFoundException

    我已经设置了 log4j12 api beta2 jar 的构建路径 但它给出了 以下错误请帮我解决这个问题我的代码如下 java 文件 package com sst log4j class Product private int pro
  • IntelliJ - 无效源版本:17

    我已经在 IntelliJ 中使用 Gradle 创建了一个使用 Java 17 的新 Java 项目 运行我的应用程序时出现错误Cause error invalid source release 17 我的设置 我已经安装了openjd
  • gwt 文本框添加更改处理程序

    我有一个从设计师那里收到的文本框 但是我在 GWT 中编写了操作 问题是文本框为空 但是当通过按下按钮用值填充文本框时 将显示警报框 通知值已更改 但没有成功 帮助我 TextBox zip1 null function onModuleL
  • “强制更新快照/版本” - 这是什么意思

    在 Maven 项目中 选择 更新项目 时 有一个名为 强制更新快照 版本 的选项 它有什么作用 强制更新快照 版本 就像运行以下命令 mvn U install U 也可以用作 update snapshot 看here http boo
  • 短 2 个字节

    我正在从串行端口读取一个长度为 133 字节的数据包 最后 2 个字节包含 CRC 值 我使用 Java 将 2 个字节值制成单个 我认为很短 这就是我所做的 short high 48 0x00ff short low 80 short
  • Web 服务客户端的 AXIS 与 JAX-WS

    我决定用Java 实现Web 服务客户端 我已经在 Eclipse 中生成了 Axis 客户端 并使用 wsimport 生成了 JAS WS 客户端 两种解决方案都有效 现在我必须选择一种来继续 在选择其中之一之前我应该 考虑什么 JAX
  • java mysql 准备好的语句

    我正在尝试使用 java 向数据库中进行简单的插入 它告诉我我的 sql 语法已关闭 但是 当我复制打印出来的字符串并将其放入 phpmyadmin 中的 sql 命令中时 它会正确执行该命令 并且我似乎无法弄清楚 java 中的字符串查询
  • 背景图像隐藏其他组件,例如按钮标签等,反之亦然

    如何解决此代码中组件的隐藏问题 代码运行没有错误 但背景图片不显示 如何更改代码以获取背景图像 使用验证方法时 它在validation 中创建错误 public class TEST public TEST String strm Jan
  • Android Webview:无法调用确定的可见性() - 从未见过 pid 的连接

    我有一个 Android Webview 当我单击链接下载文件 pdf 图像等 时 我收到一条错误消息 Error message Cannot call determinedVisibility never saw a connectio

随机推荐

  • iPhone 本地化 - 某些本地化的 XIB 无法加载

    我制作了一个具有本地化版本的 iPhone 应用程序 它大部分工作正常 但有两个视图无法加载本地化 NIB 使用标准 NIB 英文 我确信我正确地进行了本地化 获取信息 使文件可本地化 添加本地化 添加 pl 波兰语 然后编辑创建的 NIB
  • 替换嵌套数组 ruby​​ 中的元素

    我无法在代码中找到问题所在 如果特定元素出现在宾果板上 我想用 X 替换它们 class BingoBoard def initialize board bingo board board end def number letter let
  • 如何在C++中默认初始化内置类型的局部变量?

    如何在 C 中默认初始化原始类型的局部变量 例如 如果 a 有一个 typedef typedef unsigned char boolean that s Microsoft RPC runtime typedef 我想更改以下行 boo
  • 从 N 个数中找出最大和第二大的数

    给定 n 个数字 如何使用最多 n log n 次比较找到最大和第二大数字 请注意 这不是 O n log n 而是真正的 n log n 次比较 帕杰顿发表了评论 让我详细说明一下 正如帕杰顿所说 这可以通过锦标赛选择来完成 可以将其视为
  • 移动返回时调用的构造函数而不是复制

    今天我发现这段代码并没有像我期望的那样工作 根据我的知识 对于 L 值 应该调用复制构造函数 而对于 R 值 应该选择移动构造函数 否则目的何在std move它实际上什么也不做 只是转换为 R 值 我正期待着return obj将调用复制
  • GROUP BY DESC 如何选择顺序?

    所以我正在为一家商店创建部分 如果没有 商店可以有多个范围section identifier为给定设置store id它应该回退到全局商店0 我想要的 SQL 命令应该返回一个列表section options对于任何相关的给定商店 我的
  • 按行与按列访问矩阵元素

    一个矩阵A i j 给出 如果我们想将矩阵的元素相加 哪种方法更好 为什么 列明智 row wise 从我的角度来看 行方式更好 因为在数组表示中元素存储在连续的内存位置中 因此访问它们需要更少的时间 但是由于在 RAM 中获取每个位置需要
  • 单击按钮循环浏览 Jlabel 图像时出现 for 循环问题

    在java应用程序中 我有一个Jlabel 每次单击按钮时我想为其分配一个新图像 使用for循环我可以让它只显示最后一个图像 跳过图像之间的所有图像 我知道有一个错误按照我的逻辑 也许我不应该使用 for 循环 任何建议 private S
  • 捕获信号时强制终端不打印 Ctrl 热键

    再会 我正在为我的学校用 C 语言编写自己的 shell 它必须类似于bash尽可能接近 我必须处理 Ctrl 和 Ctrl C 等信号bash做 因此我可以使用signal功能 它工作正常 但问题是每当捕获 Ctrl C 信号时 从第二个
  • Json 对象的最大长度 Asp.net Core 3.1

    虽然这是大约两年前提出的问题 但我仍然面临着这个问题 而且没有办法摆脱它 有没有办法在 Asp net Core 3 1 中设置 JSON 对象的最大大小 在除 Net core 之外的其他 Net 框架中 有一些方法可以做到这一点 或者我
  • 在 Heroku 上的 Rails 应用程序中使用 COPY FROM 和 Postgresql 后端

    我想让用户可以选择在 Ruby on Rails 3 2 应用程序中上传文件 并将数据存入数据库 我想用COPY FROM命令 因为它比插入 ruby 对象更快 If I do User connection execute COPY us
  • 如何在php中通过特殊字符连接两个数组元素值?

    我有两个数组如下 Array 0 gt 2013 07 09 1 gt 2013 07 16 2 gt 2013 07 23 3 gt 2013 07 30 Array 0 gt 2013 07 16 1 gt 2013 07 23 2 g
  • Mongodb 多重嵌套数组搜索

    我的目标是搜索数据userid 1的记录 以下是我的数据 id 2 name test data id 1 file nic userid 1 2 id 2 file nic1 userid 1 id 3 file nick2 userid
  • 单击元素角度 4 外部时隐藏

    我为垂直导航创建了一个侧面菜单 因此我在单击时切换侧面菜单 我需要在单击该菜单之外的任何位置时关闭该菜单 我尝试安装 https github com chliebel angular2 click outside 但由于某种原因它不起作用
  • Spring AOP 在 Java 8 中给出 IllegalArgumentException

    使用 Java 8 和 Spring AOP 4 0 6 我收到以下错误 java lang RuntimeException Error scanning file MonitorAroundPerformance class at or
  • 提交和重定向后输入字段未清空?

    当我想从 jsf 页面 删除飞机 时 如果成功删除飞机 我会重定向到该页面并显示一条警报 告诉用户飞机已成功删除 但是 用户在 h inputText 中输入的值仍然存在 它们不是空的 JSF 页面
  • 使用 __getattr__ 覆盖魔术方法

    我有一个类 它是成员的容器 所有成员都属于同一类型 class A int def init self n super init self n n def do self print adding 10 return self n 10 c
  • .delegate=self 是什么意思?

    谁能解释一下的意思someViewController delegate self and self delegate 他们在哪里帮助我们 代表们发送信息 to you 例如 如果您使用加速度计委托 您将收到有关加速度计的消息 如果您使用新
  • 设计关系数据库 - 使用分层数据模型还是避免使用它们?

    我正在设计一个数据库 我对在关系数据库中使用分层数据模型有一些疑问 如果我想处理类别 子类别和父类别 可以不在关系数据库中使用分层数据模型吗 换句话说 是否可以使用关系方式处理类别 子类别和父类别 顺便说一句 我正在使用 PostgreSQ
  • 使用双重检查锁定实现单例时,我们是否需要 易失性

    假设我们使用双重检查锁来实现单例模式 private static Singleton instance private static Object lock new Object public static Singleton getIn