且构网

分享程序员开发的那些事...
且构网 - 分享程序员编程开发的那些事

为什么在CopyOnWriteArrayList中需要setArray()方法调用

更新时间:2021-07-09 21:46:04

此代码使用深Java内存模型voodoo,因为它混合锁和挥发性。

This code uses deep Java Memory Model voodoo, as it mixes both locks and volatiles.

这段代码中的锁使用很容易被省略。锁定在使用相同锁的线程之间提供内存排序。特别地,在该方法结束时的解锁提供了与获得相同锁的其他线程的发生在之前的语义。但是,通过这个类的其他代码路径根本不使用这个锁。因此,锁的内存模型含义与这些代码路径无关。

The lock usage in this code is easy to dispense with, though. Locking provides memory ordering among threads that use the same lock. Specifically, the unlock at the end of this method provides happens-before semantics with other threads that acquire the same lock. Other code paths through this class, though, don't use this lock at all. Therefore, the memory model implications for the lock are irrelevant to those code paths.

这些其他代码路径使用volatile读写,特别是 array 字段。 getArray 方法对此字段执行volatile读取, setArray 方法方法对此字段执行volatile写入。

Those other code paths do use volatile reads and writes, specifically to the array field. The getArray method does a volatile read of this field, and the setArray method method does a volatile write of this field.

这段代码调用 setArray 的原因显然是不必要的,因为它为这个方法建立了一个不变量它始终对此数组执行易失性写入。这与其他线程建立发生在之前的语义,该线程从该数组执行易失性读取。这是重要的,因为易失性写入 - 读取语义适用于除了易失性字段本身的读取和写入之外的读取和写入。特别地,在易失性写入发生之前对其他(非易失性)字段的写入在对相同易失性变量的易失性读取之后从这些其他字段读取。有关说明,请参见 JMM常见问题

The reason this code calls setArray even when it's apparently unnecessary is so that it establishes an invariant for this method that it always performs a volatile write to this array. This establishes happens-before semantics with other threads that perform volatile reads from this array. This is important because the volatile write-read semantics apply to reads and writes other than those of the volatile field itself. Specifically, writes to other (non-volatile) fields before a volatile write happen-before reads from those other fields after a volatile read of the same volatile variable. See the JMM FAQ for an explanation.

下面是一个例子:

// 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)
}

让我们假设行通过行(2),内部字符串x。 (为了这个例子,我们使用了内部字符串的标识语义。)假设这是真的,那么存储器模型保证在行(4)读取的值将是由行(1)设置的1。这是因为在(2)处的易失性写入,以及每个早先的写入发生在第(3)行的易失性读取之前,并且随后的每个读取。

Let's assume that line (3) gets the value set by line (2), the interned string "x". (For the sake of this example we use identity semantics of interned strings.) Assuming this is true, then the memory model guarantees that the value read at line (4) will be 1 as set by line (1). This is because the volatile write at (2), and every earlier write, happen-before the volatile read at line (3), and every subsequent read.

假设初始条件是列表已经包含单个元素,实现的字符串x。进一步假设 set()方法的 else 子句没有使 setArray 调用。现在,根据列表的初始内容,在行(2)处的 list.set()调用可能或可能不执行易失性写入,因此在行(4)可能有或没有任何可见性保证!

Now, suppose that the initial condition were that the list already contained a single element, the interned string "x". And further suppose that the set() method's else clause didn't make the setArray call. Now, depending on the initial contents of the list, the list.set() call at line (2) might or might not perform a volatile write, therefore the read at line (4) might or might not have any visibility guarantees!

显然,你不希望这些内存可见性保证取决于列表的当前内容。为了在所有情况下建立保证, set()需要在所有情况下执行volatile写入,这就是为什么它调用 setArray code>即使它没有做任何写作本身。

Clearly you don't want these memory visibility guarantees to depend upon the current contents of the list. To establish the guarantee in all cases, set() needs to do a volatile write in all cases, and that's why it calls setArray() even if it didn't do any writing itself.