代码之家  ›  专栏  ›  技术社区  ›  maxim1

Java中的易失阵列、内存障碍和可见性

  •  2
  • maxim1  · 技术社区  · 8 年前

    我很难理解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 ,它应该足以使用强制执行缓存一致性操作 任何 挥发场。这种推理正确吗?

    编辑:只有一个写线程和多个读线程。

    3 回复  |  直到 8 年前
        1
  •  3
  •   Marko Topolnik    8 年前

    不,您的代码是线程不安全的。可使其安全的变体如下:

    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() 中间没有写 整数 这是第二条线观察到的。没有记忆障碍不会 防止 观察一个动作的另一个线程。这使得结果不明确。

    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

        2
  •  1
  •   Markus Kull    8 年前

    一般来说,它不起作用的原因有很多:

    • Java没有提到内存障碍或顺序 属于 不相关的 变量。全球记忆障碍是 x86(x86)
    • 即使存在全局内存障碍:数组引用和索引数组值的写入顺序也未定义。可以保证这两种情况都发生在记忆屏障之前,但顺序是什么?非同步读取可能会看到引用,但不会看到数组值。如果有多个读/写操作,你的阅读障碍在这里没有帮助。
    • 小心引用数组:需要特别注意引用值的可见性

    更好的方法是将数组本身声明为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];
    }
    
        3
  •  0
  •   Subhomoy Sikdar    8 年前

    除非您声明变量 volatile 不能保证线程会得到正确的值。Volatile保证变量中的变化是可见的,这意味着它不会使用CPU缓存,而是从主内存中写入/读取。 您还需要 synchronization 这样,在写入完成之前,读取线程不会读取。使用数组而不是ArrayList对象的任何原因,因为您已经在使用 Arrays.copyOf 和调整大小?