我研究了CopyOnWriteArrayList的OpenJDK source code,似乎所有写操作都受到相同锁的保护,而读操作根本不受保护。据我所知,在JMM下,对变量的所有访问(包括读和写)都应该受到锁的保护,否则可能会出现重新排序的效果。
例如,set(int, E)方法包含以下行(在锁定状态下):
/* 1 */ int len = elements.length;
/* 2 */ Object[] newElements = Arrays.copyOf(elements, len);
/* 3 */ newElements[index] = element;
/* 4 */ setArray(newElements);另一方面,get(int)方法只支持return get(getArray(), index);。
在我对JMM的理解中,这意味着如果语句1-4被重新排序为1-2(new)-4-2(copyOf)-3,那么get可能会观察到处于不一致状态的数组。
我对JMM的理解有误吗?为什么CopyOnWriteArrayList是线程安全的,还有其他解释吗?
发布于 2010-06-01 23:05:34
如果你看一下底层的数组引用,你会发现它被标记为volatile。当发生写操作时(比如在上面的摘录中),这个volatile引用只在最后一条语句中通过setArray更新。在此之前,任何读操作都将返回数组的旧拷贝中的元素。
重要的一点是,数组更新是一个原子操作,因此读取将始终看到处于一致状态的数组。
只为写操作取出锁的优点是提高了读操作的吞吐量:这是因为CopyOnWriteArrayList的写操作可能非常慢,因为它们涉及复制整个列表。
发布于 2010-06-01 23:05:14
获取数组引用是一个原子操作。因此,读者将看到旧数组或新数组--无论哪种方式,状态都是一致的。(set(int,E)在设置引用之前计算新的数组内容,因此在进行指定时数组是一致的。)
数组引用本身被标记为volatile,这样读取器就不需要使用锁来查看对被引用数组的更改。(编辑:此外,volatile还保证赋值不会重新排序,这将导致在数组可能处于不一致状态时执行赋值。)
需要使用写锁定来防止并发修改,这可能会导致保存不一致数据或更改的阵列丢失。
发布于 2019-10-30 17:53:14
因此,根据Java1.8,下面是CopyOnWriteArrayList.中数组和锁的声明
/** The array, accessed only via getArray/setArray. */
private transient volatile Object[] array;
/** The lock protecting all mutators */
final transient ReentrantLock lock = new ReentrantLock();下面是CopyOnWriteArrayList的add方法的定义
public boolean add(E e) {
final ReentrantLock lock = this.lock;
lock.lock();
try {
Object[] elements = getArray();
int len = elements.length;
Object[] newElements = Arrays.copyOf(elements, len + 1);
newElements[len] = e;
setArray(newElements);
return true;
} finally {
lock.unlock();
}
}正如@Adamski已经提到的,数组是易失性的,并且只能通过setArray方法进行更新。在那之后,如果所有的只读调用都被执行了,那么它们将获得更新值,因此数组在这里总是一致的。
https://stackoverflow.com/questions/2950871
复制相似问题