java - 是否可以读取可能同时写入的变量?

标签 java multithreading thread-safety race-condition

听起来可能有些愚蠢,但我不精通Java,因此想确保:
如果有两个代码点
一世:

if (_myVar == null) 
{
    return; 
}
二:
synchronized (_myLock) 
{
    _myVar = MyVarFactory.create(/* real params */)
}
编辑:假设此_myVar是一个复杂的对象(即不是 boolean ,int或long),而是具有某些父类等的完全成熟的Java类。
假设I和II可以同时在不同的线程上运行,我认为在C++中这将是一场“数据竞赛”,但是我不确定Java的情况。

最佳答案

TL; DR:不,不是
解释:
相关文档为Java Memory Model (JMM)
JMM使JVM可以自由地为每个单独的线程在上所有对象上的每个字段创建本地缓存副本。
然后,它向每个线程递出一枚硬币。每当线程读取一个字段或写入一个字段时,它都会掷硬币。在正面,它使用其本地缓存。在尾部,它同时更新其本地缓存和“真实”副本。
此外,硬币是邪恶。它实际上不是随机的,但并不可靠。它可能在今天,每次在测试机上以及Beta测试的第一周内的每次都甩尾部。然后,当您向重要的潜在客户进行演示时,它就会整天,可靠地开始向您倾诉。只是..突然之间。
游戏的名称很简单:如果程序的行为取决于不良硬币掷出的结果,则会丢失
因此,要么编写无关紧要的代码(困难的),要么编写抑制翻转的代码(更容易的)。
通常,最简单的方法是永远不要拥有您同时写入和读取的任何字段。这听起来似乎是不可能的,但实际上非常容易:像fork fork这样的自上而下的框架通过堆栈进行所有通信(因此,方法参数传递和方法返回值),当然还有旧的,尝试过的和真正的技巧。 :通过对并发操作有出色支持的 channel 进行所有通信,例如关系数据库(如postgres)或消息队列(如Rabbitmq)。
如果必须以并发方式使用多个线程中的同一字段,那么确保不翻转恶币的唯一方法是建立所谓的“发生前/发生后”关系(这是使用的正式术语)在JMM中):有一些建立关系的特定方法,以使JMM正式祝福两行代码:该行肯定会在该行之后“出现”(这意味着:“后发生”这一行一定会出现)由“之前发生”的行引起的更改)。没有HBHA,会发生邪恶的硬币翻转,您可能会或可能不会看到变化,具体取决于月相。
HBHA因果关系的 list 很长,但是常见的方法是:

  • 自然的:在同一线程中运行的2位代码具有自然的HBHA关系。 JVM/CPU实际上可以自由地对代码重新排序并在需要时同时运行它们,但是JVM保证任何代码所观察到的一切都好像单个线程中的代码严格按顺序运行一样。
  • 启动线程:保证thread.start()发生在该线程中第一行代码之前。
  • synchronized:如果线程退出同步块(synchronized block),则在任何其他线程进入在同一对象引用上进行同步的同步块(synchronized block)之前发生。
  • volatile:对volatile字段进行读/写操作可建立任意顺序,但它是可靠的,并且可以设置HBHA。

  • 在您的代码示例中,绝对没有发生HBHA,因为我假设第一个代码片段在一个线程中运行,而第二个代码片段在另一个线程中运行。是的,第二个代码段使用synchronized,但是第一个不使用,并且synchronized只能与其他synchronized块一起建立HBHA(并且仅当它们在完全相同的对象上同步时)。因此,您没有HBHA。
    因此,JMM使JVM可以自由地运行您的代码段,以便您而不是观察第二个代码段完成的更新(其中_myVar已设置为某个实例),即使它可以观察到第二个线程的其他内容确实改变了。
    解决方案:设置HBHA;请使用为您执行此操作的AtomicReference,或在第一个代码段周围添加synchronized(_myLock),或者忘记使用db或Rabbitmq或fork/join或其他框架。
    注意:几乎没有任何方法可以编写测试来确认是否发生了恶性硬币翻转。您应该听取建议以免去讨论完全在线程之间共享变量字段的需求,例如结果是严重的 fork /联接,消息队列或数据库:共享字段的多线程代码趋于充满无法测试无法捕获的错误。

    关于java - 是否可以读取可能同时写入的变量?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/66896086/

    相关文章:

    我可以在主应用程序和多个线程中使用相同的套接字吗?

    c++ - 包装原子类型并确保它保持原子性

    java - 结果集和语句未在 Java 中关闭的影响

    java - Thread类的方法与Thread.currentThread()所使用的方法之间的区别?

    java - 单例类方法的并发调用

    java - 你如何使用事件调度线程?

    c# - 检查 null 线程安全吗?

    java - 调度或重定向到其他 Activity 的 Activity

    java - 是否可以在测试用例的主体中对列表进行排序以检查边界数据?

    java - 即使显示复选框,单击复选框也无法使用 Selenium webdriver