【问题标题】:Java multi-threading race-condition on a shared char array共享 char 数组上的 Java 多线程竞争条件
【发布时间】:2018-11-08 20:01:17
【问题描述】:

假设我有以下内容:

char[] shared = new char[]{'a', 'b'};
ExecutorService exec = Executors.newCachedThreadPool();
Future<?> f1 = exec.submit(() -> shared[0] = 'A');
Future<?> f2 = exec.submit(() -> shared[1] = 'B');
f1.get(); f2.get();
System.out.println(shared);

我希望这通常会打印出来:

'AB'

但是这个也可以打印吗:

'aB'

'bA'

我想知道由于shared 不是易失性的,一个线程可能在将其更改保存到其相应​​的 char 元素时,保存其在其本地处理器缓存中已过时的相邻字节,然后踩踏另一个线程所做的更改。我感觉这个问题的答案是“不”,因为我认为这会违反 Java 的内存模型,该模型说,每当一个线程被加入时,它所做的所有动作都发生在加入之前。但如果是这样的话,那么我很好奇这是如何防止的?我的理解是,当字节被刷新时,整个缓存行都会被刷新,我想一个 char[] 数组会有多个元素位于同一个缓存行中。

【问题讨论】:

  • 我认为您的意思是 shared[0]shared[1] 而不是 char[0]char[1]
  • 糟糕。固定的。谢谢!

标签: java multithreading java-memory-model


【解决方案1】:

这已在Java Language Specification 中明确指定:

17.6。字撕裂

Java 虚拟机实现的一个考虑因素是每个字段和数组元素都被认为是不同的;对一个字段或元素的更新不得与任何其他字段或元素的读取或更新交互。特别是,两个线程分别更新一个字节数组的相邻元素,不能相互干扰或交互,也不需要同步来保证顺序一致性。

某些处理器不提供写入单个字节的能力。在这样的处理器上通过简单地读取整个字,更新适当的字节,然后将整个字写回内存来实现字节数组更新是非法的。这个问题有时被称为字撕裂,在不能轻易单独更新单个字节的处理器上,需要一些其他方法。

在最坏的情况下,这意味着使用 CAS 指令或类似指令执行此类数组更新,以确保不会发生中间更新。但据我所知,x86/x64 架构不受此影响,因为它具有修改较小尺寸项目的指令,并且它将处理使其他 CPU 内核的缓存行无效以确保其他 CPU 内核将获取更改的执行自己的更新之前的数据。当然,这仍然比在不同缓存行上执行更新的两个 CPU 内核要慢。

【讨论】:

    猜你喜欢
    • 2016-10-12
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2012-04-29
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多