The main problem CopyOnWriteArrayList solves is concurrent traversal reads without locking (via Iterator)

Compare CopyOnWriteArrayList with ArrayList

What would you do if we were constantly reading a list (array) that might change?

A global ArrayList that locks when modified and locks when read

Why do I need to lock when I read?

If is unlocked when the ArrayList traversal read, then modified the ArrayList other threads (add or delete), will throw ConcurrentModificationException, this is the failfast mechanisms (we only discuss the Iterator traversal here, If it’s a normal for loop, the array might be out of bounds, which we won’t discuss here.)

An array out of bounds may occur if an array traversal is read

So the lock read is a write operation

If a lock is added to a read, then the performance for concurrent reads is obviously poor. Of course, if you say that a read/write lock can solve this problem, but what we want here is a lock free read operation that is thread safe.

The following example sets the backdrop for relatively high concurrency reads + relatively low concurrency modifications

List<Integer> arr = new CopyOnWriteArrayList<>(); //List<Integer> arr = new ArrayList<>(); For (int I = 0; i < 3; i++) { arr.add(i); } for (int I = 0; i < 1000; i++) { final int m = i; new Thread(() -> { try {Thread.sleep(1); Iterator<Integer> Iterator = arr.iterator(); Iterator<Integer> Iterator = arr.iterator(); try {Thread.sleep(new Random().nextInt(10)); } catch (InterruptedExcep{} int count = 0; while(iterator.hasNext()){ iterator.next(); count++; } System.out.println("read:"+count); }).start(); } // multithread write for (int ii = 0; ii < 10; ii++) { new Thread(() -> { arr.add(123); System.out.println("write"); }).start(); }Copy the code

The above example will return an error if replaced with an ArrayList because:

Because the next() method will call the checkForComodification check, the modCount (original arrayList) is inconsistent with the expectedModCount, which is the above mentioned fast failure. This fast failure means that an exception is thrown whenever an inconsistency is found, regardless of whether there is currently a concurrency situation or problem

The solution to ArrayList is to lock iterator

final void checkForComodification() { if (modCount ! = expectedModCount) throw new ConcurrentModificationException(); }Copy the code

So why is CopyOnWriteArrayList ok? Instead of looking at CopyOnWrite, let’s look at the iterator of CopyOnWriteArrayList

public Iterator<E> iterator() {
     return new COWIterator<E>(getArray(), 0);
}
Copy the code

CopyOnWriteArrayList calls iterator to generate a new array snapshot, traverses the snapshot, so it never fails (even if it changes the list after reading), and CopyOnWriteArrayList has no fastfail mechanism. The reason is that Iterator’s snapshot implementation and CopyOnWrite no longer require fastfail to ensure collection correctness

CopyOnWrite of CopyOnWriteArrayList is when you modify a collection of arrays, you create a new array and make adjustments to the new data, and then you assign the new array to the old one

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; // Adjust the new array setArray(newElements); // Assign the new array to the original array return true; } finally { lock.unlock(); }}Copy the code

Why copy a new array, and what is the benefit of doing so?

If you modify the original data structure without copying the new array (locking to keep it thread-safe), then the read will be locked. If the read is not locked, it is possible to read the “half-finished” of the modified array (COWIterator(getArray(), 0); It’s a work-in-progress.)

If you copy a new array, even if the modification is not complete, you will still get the old array, so there will be no problem.

The main use of this class is to avoid locking iterator methods on collections. Let’s take a look at an excerpt of this class’s comments:

  • making a fresh copy of the underlying array.This is ordinarily too costly, but may be more efficient
  • than alternatives when traversal operations vastly outnumber
  • mutations, and is useful when you cannot or don’t want to
  • synchronize traversals, yet need to preclude interference among
  • concurrent threads.
  • This array never changes during the lifetime of the
  • iterator, so interference is impossible and the iterator is
  • guaranteed not to throw {@code ConcurrentModificationException}.
  • The iterator will not reflect additions, removals, or changes to
  • the list since the iterator was created.

Translation:

Copying a new array may seem expensive, but it’s effective when the number of traversals far exceeds the number of changes, and it’s even more useful when you don’t want to use synchronized traversals

This new copy of the array in the iterator will never change, life cycle and the iteration is won’t make ConcurrentModificationException anomalies

Once an iterator is created, it cannot be modified (adding or removing elements).

Let’s extract the author’s thoughts:

This class is thread-safe to use

2. Iterator traversal is error-free and lock-free

3. On the premise of writing less and reading more, it is more appropriate