读取一个可能同时写入的变量是否可以?

Is it OK to read a variable that could potentially be written at the same time?

可能听起来有点傻,但我不精通Java所以想确定一下:

如果有两个码位

我:

if (_myVar == null) 
{
    return; 
}

二:

synchronized (_myLock) 
{
    _myVar = MyVarFactory.create(/* real params */)
}

编辑:假设这个 _myVar 是一个复杂的对象(即 不是 布尔值、整数或长整数)但是一个完全成熟的 java class 有一些父 classes 等 .

假设我和 II 可以同时在不同的线程上 运行,我认为在 C++ 中这将是一场“数据竞争”,但是我不确定 [=27] 中的情况=].

不,不是线程安全的。

数据竞争只是问题之一。您可能遇到 CPU core cache 可见性问题。

如果您的变量是 boolean、int 或 long(或等效的对象)类型,请使用适当的 Atomic… class。这些 classes 包装它们的内容以进行线程安全访问。

如果不是这些类型,请使用 AtomicReference to contain your object in a thread-safe 方式。

除了 Atomic… class 之外,还有其他技术。

所有这些都已在 Stack Overflow 上多次提及。所以搜索以了解更多信息。

并阅读 Brian Goetz 等人 classic 书,Java Concurrency in Practice

TL;DR: 不,不行

解释:

相关文档是 Java Memory Model (JMM).

JMM 允许 JVM 自由地为 all objects 上的每个字段创建本地缓存副本对于每个单独的线程。

然后,它给每个线程一枚硬币。每当线程读取一个字段或写入一个字段时,它都会抛硬币。在头部,它使用其本地缓存。在尾巴上,它会更新其本地缓存以及 'real' 副本。

此外,该币邪恶。它实际上不是随机的,但它是不可靠的。它可能会在今天、每次在测试机上以及在 Beta 的第一周内每次都翻转尾巴。然后就在您向那个重要的潜在客户进行演示时,它开始向您抛头露面,可靠地,整天,每次。只是..突然.

游戏的名字很简单:如果你的程序的行为取决于邪恶的掷硬币的结果,你就输了

因此,要么编写不关心的代码(困难),要么编写抑制翻转的代码(更容易)。

一般来说,最简单的做法是从不任何字段,你可以同时写入和读取。这听起来不可能,但实际上非常简单:Top-down 像 fork join 这样的框架通过堆栈进行 all 通信(因此,方法参数传递和方法 return values),当然还有那个古老的、尝试过的、真正的技巧:通过一个对并发操作有很好支持的通道进行所有通信,比如像 postgres 这样的关系数据库,或者像 rabbitmq 这样的消息 queue。

如果您必须以并发方式从多个线程使用同一个字段,确保不抛出邪恶硬币的唯一方法是建立 so-called 'Happens-Before/Happens-After' 关系(这是官方的JMM 中使用的术语):有一些特定的方法可以建立关系,使得 JMM 正式祝福 2 行代码:那行肯定 'happen after' 那行(这意味着:那行 'happens after' 肯定会观察到由 'happens before') 行引起的变化。没有 HBHA,就会发生邪恶的硬币翻转,您可能会或可能不会看到变化,具体取决于月相。

HBHA因果关系列表很长,但常见的方式:

  • 自然:2位代码运行ning在同一个线程中有自然的HBHA关系。 JVM/CPU 实际上可以自由地同时使用 re-order 代码和 运行 东西,但是 JVM 保证任何代码观察到的任何东西都好像是单个线程中的代码 运行严格按顺序排列。
  • 启动线程:thread.start() 保证 happen-before 该线程中的第一行代码。
  • synchronized:如果一个线程退出一个同步块,那么 happens-before 进入同步块的任何其他线程都在同一个 object 引用上进行同步。
  • volatile:Reads/writes对volatile字段建立任意顺序,但可靠,并设置HBHA。

在您的代码示例中,绝对没有进行 HBHA,因为我假设第一个片段 运行 在一个线程中,第二个片段 运行 在另一个线程中。是的,第二个片段使用 synchronized,但第一个片段没有,并且 synchronized 只能与其他 synchronized 块建立 HBHA(并且只有当它们在完全相同的 object).因此,你没有HBHA。

因此,JMM 赋予 JVM 运行 您的代码片段的自由,这样您 不会 观察第二个代码片段完成的更新(其中 _myVar 设置为某个实例),即使它可以观察到第二个线程确实更改的其他内容。

解决方案:设置 HBHA;使用为您完成的 AtomicReference,或者在第一个代码段周围抛出 synchronized(_myLock),或者忘记它并使用 db 或 rabbitmq 或 fork/join 或其他一些框架。

注意:几乎没有办法编写测试来确认正在发生恶意抛硬币。您应该听取建议,考虑避免完全讨论线程之间共享可变字段的需要,例如fork/join、消息 queues 或数据库严重后果:共享字段的多线程代码往往充满了测试无法捕获的错误。