同步块中易失性数组写入的必要性

Tre*_*man 8 java multithreading thread-safety

有关JMM的问题以及有关在同步块中写入但是读取未同步的易失性字段的语义.

在下面代码的初始版本中,我没有同步访问,因为它不需要早期的需求(并且滥用自我赋值this.cache = this.cache确保了volatile写入语义).某些要求已更改,需要同步以确保不发送重复更新.我的问题是同步块是否排除了要求自动分配易失性字段?

  // Cache of byte[] data by row and column.
  private volatile byte[][][] cache;

  public byte[] getData(int row, int col)
  {
    return cache[row][col];
  }

  public void updateData(int row, int col, byte[] data)
  {
    synchronized(cache)
    {
      if (!Arrays.equals(data,cache[row][col]))
      {
        cache[row][col] = data;

        // Volatile write.
        // The below line is intentional to ensure a volatile write is
        // made to the array, since access via getData is unsynchronized.
        this.cache = this.cache;

        // Notification code removed
        // (mentioning it since it is the reason for synchronizing).
      }
    }
  }
Run Code Online (Sandbox Code Playgroud)

如果没有同步,我认为自我分配易失性写入在技术上是必要的(尽管IDE将其标记为无效).使用synchronized块,我认为它仍然是必要的(因为读取是不同步的),但我只想确认,因为如果它实际上不需要它在代码中看起来很荒谬.我不确定在同步块结束和易失性读取之间是否有任何保证我不知道.

irr*_*ble 4

是的,根据 Java 内存模型,您仍然需要易失性写入。cache 从解锁到后续的易失性读取没有同步顺序cache解锁 -> 易失性读取不保证可见性。您需要解锁 - >锁定易失性写入 - >易失性读取

然而,真正的 JVM 具有更强的内存保证。通常,unlockvolatileWrite具有相同的内存效应(即使它们位于不同的变量上);与lockvolatileRead相同。

所以我们这里陷入了两难的境地。典型的建议是您应该严格遵循规范。除非你对此事有非常广泛的了解。例如,JDK 代码可能会使用一些理论上不正确的技巧;但代码是针对特定的 JVM 并且作者是专家。

无论如何,额外的易失性写入的相对开销似乎并没有那么大。

你的代码正确且高效;然而它不符合典型模式;我会稍微调整一下:

  private final    byte[][][] cacheF = new ...;  // dimensions fixed?
  private volatile byte[][][] cacheV = cacheF;

  public byte[] getData(int row, int col)
  {
    return cacheV[row][col];
  }

  public void updateData(int row, int col, byte[] data)
  {
    synchronized(cacheF)
    {
      if (!Arrays.equals(data,cacheF[row][col]))
      {
        cacheF[row][col] = data;

        cacheV = cacheF; 
      }
    }
  }
Run Code Online (Sandbox Code Playgroud)