读取一个可能同时写入的变量是否可以?
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 或数据库严重后果:共享字段的多线程代码往往充满了测试无法捕获的错误。
可能听起来有点傻,但我不精通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 或数据库严重后果:共享字段的多线程代码往往充满了测试无法捕获的错误。