63

I've taken a look into OpenJDK source code of CopyOnWriteArrayList and it seems that all write operations are protected by the same lock and read operations are not protected at all. As I understand, under JMM all accesses to a variable (both read and write) should be protected by lock or reordering effects may occur.

For example, set(int, E) method contains these lines (under lock):

/* 1 */ int len = elements.length;
/* 2 */ Object[] newElements = Arrays.copyOf(elements, len);
/* 3 */ newElements[index] = element;
/* 4 */ setArray(newElements);

The get(int) method, on the other hand, only does return get(getArray(), index);.

In my understanding of JMM, this means that get may observe the array in an inconsistent state if statements 1-4 are reordered like 1-2(new)-4-2(copyOf)-3.

Do I understand JMM incorrectly or is there any other explanations on why CopyOnWriteArrayList is thread-safe?

Basil Bourque
  • 303,325
  • 100
  • 852
  • 1,154
Fixpoint
  • 9,619
  • 17
  • 59
  • 78

4 Answers4

77

If you look at the underlying array reference you'll see it's marked as volatile. When a write operation occurs (such as in the above extract) this volatile reference is only updated in the final statement via setArray. Up until this point any read operations will return elements from the old copy of the array.

The important point is that the array update is an atomic operation and hence reads will always see the array in a consistent state.

The advantage of only taking out a lock for write operations is improved throughput for reads: This is because write operations for a CopyOnWriteArrayList can potentially be very slow as they involve copying the entire list.

Adamski
  • 54,009
  • 15
  • 113
  • 152
  • Thank you. I've missed the fact that the array is `volatile`. – Fixpoint Jun 01 '10 at 15:19
  • 22
    An important detail is that volatile only applies to the array reference itself, not to the content of the array. However because all changes to the array are made **before** its reference is published, the volatile guarantees extend to the content of the array. – assylias Apr 10 '13 at 13:19
  • `Up until this point any read operations...`. So, would that mean the COW iterator could still possibly see the updated value if it has not reached that item/point/position yet? – stdout Mar 19 '20 at 11:00
21

Getting the array reference is an atomic operation. So, readers will either see the old array or the new array - either way the state is consistent. (set(int,E) computes the new array contents before setting the reference, so the array is consistent when the asignment is made.)

The array reference itself is marked as volatile so that readers do not need to use a lock to see changes to the referenced array. (EDIT: Also, volatile guarantees that the assignment is not re-ordered, which would lead to the assignment being done when the array is possibly in an inconsistent state.)

The write lock is required to prevent concurrent modification, which may result the array holding inconsistent data or changes being lost.

mdma
  • 56,943
  • 12
  • 94
  • 128
  • 1
    This is not 100% accurate. The atomicity of setting the reference is not enough for guaranteeing consistency, and the Java Memory Model rules address this issue. Out of order writes and re-ordering of instructions may occur, and then a thread can receive a reference pointing to an inconsistent object. This also happens with the double-check-locking pattern (see http://www.ibm.com/developerworks/java/library/j-dcl.html) – Eyal Schneider Jun 01 '10 at 15:15
  • 3
    It's not the same. reading/writing to a volatile is consisdered a what the JMM terms a 'synchronized action' and defines a barrier on what can be reordered. See http://java.sun.com/docs/books/jls/third_edition/html/memory.html – mdma Jun 01 '10 at 15:19
  • 1
    @Eyal Schneider: Welcome to 2004 ( see http://www.ibm.com/developerworks/library/j-jtp03304/ ). Read section titled "New guarantees for volatile" – Alexander Pogrebnyak Jun 01 '10 at 15:20
  • 1
    Exactly. But according to your response it looks like the atomicity of reference setting by itself guarantees data integrity. – Eyal Schneider Jun 01 '10 at 15:21
  • 2
    It's a question of how much detail to put in an answer. But as this has caused confusion for at least one person, I'll update my answer to make this explicit. – mdma Jun 01 '10 at 15:24
1

So according to Java 1.8, following are the declarations of array and lock in CopyOnWriteArrayList.

/** The array, accessed only via getArray/setArray. */
    private transient volatile Object[] array;

/** The lock protecting all mutators */
    final transient ReentrantLock lock = new ReentrantLock();

Following is definition of add method of CopyOnWriteArrayList

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

As @Adamski has already mentioned array is volatile and only updated via the setArray method . After that, if all the read only calls are made, and so they would be getting the updated value and hence array is always consistent here.

nagendra547
  • 5,672
  • 3
  • 29
  • 43
-2

CopyOnWriteArrayList is a concurrent Collection class introduced in Java 5 Concurrency API along with its popular cousin ConcurrentHashMap in Java.

CopyOnWriteArrayList implements List interface like ArrayList, Vector and LinkedList but its a thread-safe collection and it achieves its thread-safety in a slightly different way than Vector or other thread-safe collection class.

As name suggest CopyOnWriteArrayList creates copy of underlying ArrayList with every mutation operation e.g. add or set. Normally CopyOnWriteArrayList is very expensive because it involves costly Array copy with every write operation but its very efficient if you have a List where Iteration outnumber mutation e.g. you mostly need to iterate the ArrayList and don't modify it too often.

Iterator of CopyOnWriteArrayList is fail-safe and doesn't throw ConcurrentModificationException even if underlying CopyOnWriteArrayList is modified once Iteration begins because Iterator is operating on separate copy of ArrayList. Consequently all the updates made on CopyOnWriteArrayList is not available to Iterator.

To get the most updated version do a new read like list.iterator();

That being said, updating this collection alot will kill performance. If you tried to sort a CopyOnWriteArrayList you'll see the list throws an UnsupportedOperationException (the sort invokes set on the collection N times). You should only use this read when you are doing upwards of 90+% reads.

Ankur Lathi
  • 7,636
  • 5
  • 37
  • 49