编程语言
首页 > 编程语言> > java – 为什么在CopyOnWriteArrayList中需要setArray()方法调用

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