java – 为什么在CopyOnWriteArrayList中需要setArray()方法调用
作者:互联网
在CopyOnWriteArrayList.java中,在方法集中(int index,E element)
下面
public E set(int index, E element) {
final ReentrantLock lock = this.lock;
lock.lock();
try {
Object[] elements = getArray();
Object oldValue = elements[index];
if (oldValue != element) {
int len = elements.length;
Object[] newElements = Arrays.copyOf(elements, len);
newElements[index] = element;
setArray(newElements);
} else {
// Not quite a no-op; ensures volatile write semantics
setArray(elements);----? Why this call required?
}
return (E)oldValue;
} finally {
lock.unlock();
}
}
为什么需要调用setArray()?我无法理解上面那个方法调用的注释.是因为我们没有使用synchronized块,我们必须手动刷新我们使用的所有变量吗?在上面的方法中,他们使用了重入锁.如果他们使用了synchronized语句,他们仍然需要调用setArray()方法吗?我觉得不行.
问题2:如果我们最终在else中,这意味着我们没有修改元素数组,那么为什么我们需要刷新变量数组的值?
解决方法:
此代码使用深度Java内存模型voodoo,因为它混合了锁和挥发物.
但是,此代码中的锁定使用很容易省去.锁定在使用相同锁的线程之间提供内存排序.具体来说,此方法结束时的解锁提供了与其他获取相同锁的线程之前的语义.但是,通过此类的其他代码路径根本不使用此锁.因此,锁的内存模型含义与那些代码路径无关.
那些其他代码路径确实使用易失性读写,特别是数组字段. getArray方法对此字段执行易失性读取,setArray方法方法对此字段执行易失性写入.
这个代码调用setArray的原因,即使它显然是不必要的,因此它为这个方法建立了一个不变量,它总是对这个数组执行易失性写操作.这与其他执行此数组的易失性读取的线程建立了语义之前的事件.这很重要,因为易失性写 – 读语义适用于除volatile字段本身之外的读写操作.具体而言,在易失性写入发生之前写入其他(非易失性)字段 – 在对相同易失性变量的易失性读取之后从那些其他字段读取之前.有关说明,请参阅JMM FAQ.
这是一个例子:
// initial conditions
int nonVolatileField = 0;
CopyOnWriteArrayList<String> list = /* a single String */
// Thread 1
nonVolatileField = 1; // (1)
list.set(0, "x"); // (2)
// Thread 2
String s = list.get(0); // (3)
if (s == "x") {
int localVar = nonVolatileField; // (4)
}
假设line(3)得到由行(2)设置的值,即实习字符串“x”. (为了这个例子,我们使用内部字符串的标识语义.)假设这是真的,那么内存模型保证第(4)行读取的值将是1(由行(1)设置).这是因为在(2)和每个早期写入的易失性写入发生在第(3)行的易失性读取之前以及每次后续读取之前.
现在,假设初始条件是列表已经包含单个元素,实习字符串“x”.并且进一步假设set()方法的else子句没有进行setArray调用.现在,根据列表的初始内容,第(2)行的list.set()调用可能会也可能不会执行易失性写入,因此第(4)行的读取可能有也可能没有任何可见性保证!
显然,您不希望这些内存可见性保证依赖于列表的当前内容.为了在所有情况下建立保证,set()需要在所有情况下执行易失性写入,这就是为什么它调用setArray(),即使它没有自己写任何文件.
标签:java-memory-model,java,collections,locks 来源: https://codeday.me/bug/20191004/1852730.html