为 CopyOnWriteArrayList 中的添加操作获取锁

Acquiring locks for add operation in CopyOnWriteArrayList

为什么我们在List中添加元素时需要按照CopyOnWriteArrayList中的以下代码获取Reentrant锁。我们正在创建原始数组的副本,然后对其进行修改。如果我们不首先获得 lock,我们会有什么副作用?

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();
        }
    }

当您尝试在多线程上下文中对全局变量执行任何操作并希望它既是原子又确保内存可见性 到其他线程,您需要对该操作进行锁定。

这里 getArray() 正在返回一个全局实例字段 Object[] array

所以在这个例子中:

Object[] elements = getArray();
int len = elements.length;
Object[] newElements = Arrays.copyOf(elements, len + 1);
newElements[len] = e;
setArray(newElements);
return true;

如果此代码块周围没有锁,并且假设两个线程正在尝试添加一个元素,在这种情况下,线程一和线程二可能会读取 相同的 len 的值并将新元素分配给相同的索引。

所以最后哪个线程分配新值将覆盖另一个线程之前设置的值。

进一步解释,假设线程一和线程二都读取相同的值 len 现在线程一继续从 Arrays.copyOf(elements, len + 1) 创建新数组并分配变量的值 e在新数组的len位置。

并且在线程一可以使用 setArray(newElements) 设置新数组之前,线程二同时使用相同的值 len 继续此过程。虽然它会创建一个新的数组实例,但设置新元素的索引将与线程一使用的 len 相同。

因此,当线程二使用 setArray(newElements) 在线程一之后使用新值设置新数组时,较早的数组值位于 lenth 索引将被线程二设置的新元素覆盖。