Java中的易失性数组和内存障碍以及可见性

时间:2016-09-09 09:55:26

标签: java multithreading volatile memory-barriers

我很难理解Java中的内存障碍和缓存一致性,以及这些概念与数组的关系。

我有以下场景,其中一个线程修改一个数组(对它的引用和它的一个内部值),另一个线程从中读取。

int[] integers;
volatile boolean memoryBarrier;

public void resizeAndAddLast(int value) {
    integers = Arrays.copyOf(integers, integers.size + 1);
    integers[integers.size - 1] = value;
    memoryBarrier = true;
}

public int read(int index) {
    boolean memoryBarrier = this.memoryBarrier;
    return integers[index];
}

我的问题是,这是否符合我的想法,即“发布”到memoryBarrier并随后读取变量强制执行缓存一致性操作并确保读者线程确实得到两者最新的数组引用和指定索引处的正确底层值?

我的理解是数组引用不必声明volatile,它应该足以使用任何 volatile字段强制执行缓存一致性操作。这种推理是否正确?

编辑:只有一个作家线程和许多读者线程。

3 个答案:

答案 0 :(得分:3)

不,你的代码是线程不安全的。使其安全的变化如下:

void raiseFlag() {
   if (memoryBarrier == true)
     throw new IllegalStateException("Flag already raised");
   memoryBarrier = true;
}

public int read(int index) {
  if (memoryBarrier == false)
     throw IllegalStateException("Flag not raised yet");
  return integers[index];
}

你只需要举起一次标志,你就不会发布多个integers数组。不过,这对你的用例来说是没用的。

现在,关于为什么 ...你不能保证read()的第一行和第二行之间没有对integers的干预写入被第二行观察到了。缺少内存屏障不会阻止另一个线程观察操作。它没有指定结果。

有一个简单的习惯用法可以使你的代码线程安全(专门假设单个线程调用resizeAndAddLast,否则需要更多代码和AtomicReference):

volatile int[] integers;

public void resizeAndAddLast(int value) {
    int[] copy = Arrays.copyOf(integers, integers.length + 1);
    copy[copy.length - 1] = value;
    integers = copy;
}

public int read(int index) {
    return integers[index];
}

在此代码中,一旦发布数组,您就永远不会触摸数组,因此,无论您从read取消引用的是什么,都会按照预期进行观察,并更新索引。

答案 1 :(得分:1)

一般情况下,它有多种原因:

  • Java对内存障碍或排序没有任何说明 无关的变量。全球记忆障碍是一种副作用 86
  • 即使存在全局内存障碍:数组引用和索引数组值的写入顺序未定义。保证两者都发生在 - 记忆障碍之前,但是以哪种顺序发生?非同步读取可能会看到引用但不会看到数组值。在多次读/写的情况下,您的读屏障不起作用。
  • 注意引用数组:引用值的可见性需要特别注意

稍微好一点的方法是将数组本身声明为volatile 将其值视为不可变:

volatile int[] integers; // volatile (or maybe better AtomicReference)

public void resizeAndAddLast(int value) {
    // enforce exactly one volatile read!
    int[] copy = integers;
    copy = Arrays.copyOf(copy, copy.size + 1);
    copy[copy.size - 1] = value; 
    // may lose concurrent updates. Add synchronization or a compareExchange-loop!
    integers = copy;
}

public int read(int index) {
    return integers[index];
}

答案 2 :(得分:0)

除非您声明变量volatile,否则无法保证线程将获得正确的值。易失性保证变量的变化是可见的,而不是使用CPU缓存,它将从主存储器写入/读取。 您还需要synchronization,以便在写入完成之前不读取读取线程。因为你已经在使用Arrays.copyOf并调整大小,所以有理由使用数组而不是ArrayList对象吗?