有关JMM的问题以及有关在同步块中写入但读取未同步的易失性字段的语义。
在下面代码的初始版本中,我没有同步访问,因为它不需要早期的需求(并且滥用自我分配this.cache = this.cache确保了易失性写入语义)。某些要求已更改,需要同步以确保不发送重复更新。我的问题是同步块是否排除了要求自动分配易失性字段?
// 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).
}
}
}
如果没有同步,我认为自我分配易失性写入在技术上是必要的(虽然IDE将其标记为无效)。使用synchronized块,我认为它仍然是必要的(因为读取是不同步的),但我只想确认,因为如果它实际上不需要它在代码中看起来很荒谬。我不确定在同步块结束和易失性读取之间是否有任何保证我不知道。
答案 0 :(得分:4)
是的,根据Java Memory Model,你仍然需要volatile写。
解锁cache
没有同步顺序
随后对cache
的易失性读取:
unlock - > volatileRead 不保证可见性。
您需要 unlock - >锁定或 volatileWrite - > volatileRead 。
但是,真正的JVM具有更强大的内存保证。通常 unlock 和 volatileWrite 具有相同的记忆效果(即使它们在不同的变量上);与 lock 和 volatileRead 相同。
所以我们在这里陷入两难境地。典型的建议是你应该严格遵守规范。除非你对此事有广泛的了解。例如,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;
}
}
}
答案 1 :(得分:3)
自我赋值确保另一个线程将读取已设置的数组引用,而不是另一个数组引用。但是你可能有一个线程修改数组而另一个线程读取它。
应该同步对数组的读取和写入。此外,我不会盲目地存储和返回缓存中的数组。数组是一个可变的,非线程安全的数据结构,任何线程都可能通过改变数组来破坏缓存。您应该考虑创建防御性副本,和/或返回不可修改的列表而不是字节数组。
答案 2 :(得分:3)
对易失性数组的索引写入实际上没有记忆效应。也就是说,如果您已经实例化了数组,那么将字段声明为volatile将不会为您提供在分配给数组中的元素时所寻找的内存语义。
换句话说
private volatile byte[][]cache = ...;
cache[row][col] = data;
具有与
相同的内存语义private final byte[][]cache = ...;
cache[row][col] = data;
因此,您必须同步对阵列的所有读取和写入。当我说'相同的内存语义'时,我的意思是不能保证线程会读取最新值cache[row][col]