【问题标题】:unsynchronized read/write of variables may cause data race?变量的不同步读/写可能导致数据竞争?
【发布时间】:2019-12-29 03:54:53
【问题描述】:

在 Jack Shirazi 的 Java Performance Tuning 中写道:

这意味着变量的访问和更新是自动同步的(只要它们不是长整数或双精度数)。如果一个方法只包含一个变量访问或赋值,没有必要为了线程安全而使其同步,并且有充分的理由不这样做是为了提高性能。线程安全进一步扩展到独立于任何其他变量值访问或分配给变量的任何语句集。

根据上面的描述,像flag = true 这样的操作总是原子,不需要synchronize

然而,another article 将以下情况视为数据竞赛


class DataRaceExample {
  static boolean flag = false;//w0
  static void raiseFlag() {
    flag = true;//w1
  }
  public static void main(String... args) {
    ForkJoinPool.commonPool().execute(DataRaceExample::raiseFlag);
    while (!flag);//r_i, where i ∈ [1, k), k may be infinite
    System.out.print(flag);//r
  }
}

作者说:

现在,所有执行都有数据竞争,因为标志不是易失性的

这两篇文章之间的冲突让我很困惑。

【问题讨论】:

    标签: java concurrency thread-safety data-race non-thread-safe


    【解决方案1】:

    Jack Shirazi 错了。

    原始变量(例如int)的访问和更新是原子的,但不是同步的

    因为它是原子的,所以可以通过将其设为volatile 使其完全线程安全。否则,在不同内核上运行的其他线程可能会看到 stale 值,因为 CPU 缓存 尚未刷新。

    【讨论】:

    • 他并没有错,不幸的是,他使用了非标准的、JSR-133 之前的术语来描述原始类型的读/写原子性。
    【解决方案2】:

    Jack Shirazi 试图指出的一点是,对 doublelong 以外的原始类型的非易失性访问保证根据 JMM 以原子方式执行。因此,同步对于防止例如在存在并发访问时的读取和写入撕裂时是不必要的。

    之所以出现混淆,是因为他的书早于 JSR-133,并且他使用了诸如“自动同步”之类的术语,这与 JMM 中的现代同步概念不符。

    【讨论】:

      【解决方案3】:

      在您的第二个示例中,循环将不会运行或永远运行。

      这样做的原因是变量标志在第一次检查时只被读取一次。

      如果标志是易失性的,则每次都从内存中读取。这允许另一个线程更改 flag 的值,循环将看到它。

      【讨论】:

      • 如果 boolean 的读/写是 atomic ,那么写入应该总是 visible 以在另一个线程中读取。我知道cpu有缓存,但是如果java保证原子操作的可见性,这里应该没有问题,这里不需要volatile
      • 问题在于,如果标志不是易失性的,它只会从内存中读取一次。即使它是从另一个线程更改的,循环也不会重​​新读取更改变量。
      • 是的,读取是原子的。然而,这仅仅意味着从内存中读取是在一个有保证的单一动作中完成的。这并不意味着它总是从内存中读取。
      • 但是您使用的是普通布尔值而不是 AtomicBoolean。
      • 变量标志可能只读一次。规范不需要这种行为。
      猜你喜欢
      • 2019-04-22
      • 1970-01-01
      • 2015-05-21
      • 1970-01-01
      • 1970-01-01
      • 2016-07-22
      • 2021-09-05
      • 1970-01-01
      • 2021-09-03
      相关资源
      最近更新 更多