Search code examples
javajava.util.concurrentcopyonwritearraylist

Question about the source of CopyOnWriteArrayList#addIfAbsent, why gets the array again is needed


I am learning the java concurrent package.
After reading the source of CopyOnWriteArrayLis, I have a following question.

private boolean addIfAbsent(E e, Object[] snapshot) {
        final ReentrantLock lock = this.lock;
        // Here is my question.
        lock.lock();
        try {
            Object[] current = getArray();
            int len = current.length;
            if (snapshot != current) {
                // Optimize for lost race to another addXXX operation
                int common = Math.min(snapshot.length, len);
                for (int i = 0; i < common; i++)
                    if (current[i] != snapshot[i] && eq(e, current[i]))
                        return false;
                if (indexOf(e, current, common, len) >= 0)
                        return false;
            }
            Object[] newElements = Arrays.copyOf(current, len + 1);
            newElements[len] = e;
            setArray(newElements);
            return true;
        } finally {
            lock.unlock();
        }
    }

My question is why the Optimize is needed ?
Of cource, I have googled myself, and the answer is always to make it right when other threads may have added new elements.

But how to explain the lock.lock()? When one thread has got the lock, how can other threads add new elements ?

I know may be it is a stupid question, but I am really confusing about that.


Solution

  • As you probably saw, the snapshot is taken in this method

    public boolean addIfAbsent(E e) {
            Object[] snapshot = getArray();
            return indexOf(e, snapshot, 0, snapshot.length) >= 0 ? false :
                addIfAbsent(e, snapshot);
        }
    

    Which at the end calls the method you put in your question.

    So, if there are manipulations of the array between the snapshot being taken and the lock being locked by the current thread, they have to be handled correctly.
    There are various ways how such a manipulation could happen inbetween those two points in time, e.g. the thread that calls the addIfAbsent method is interrupted by the scheduler.
    Another not so unlikely situation, if the list is being written to frequently, would be that the lock is actually locked by another thread when the current thread tries to lock it, so it has to wait until the other thread has finished its operation (which could have added an element to the list) and unlocked the lock, before it can lock the lock itself.