我很难理解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字段强制执行缓存一致性操作。这种推理是否正确?
编辑:只有一个作家线程和许多读者线程。
答案 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)
一般情况下,它有多种原因:
稍微好一点的方法是将数组本身声明为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对象吗?