Reputation: 14126
An abstract from JCIP
Iterators for the copy-on-write collections retain a reference to the backing array that was current at the start of iteration, and since this will never change, they need to synchronize only briefly to ensure visibility of the array contents.
The iterators returned by the copy-on-write collections do not throw ConcurrentModificationException and return the elements exactly as they were at the time the iterator was created, regardless of subsequent modifications
Looking at the source code at #CopyOnWriteArrayList.iterator()
956 public Iterator<E> iterator() {
957 return new COWIterator<E>(getArray(), 0); // call to private constructor
958 }
993 private final Object[] snapshot;
994
995 private int cursor;
996
997 private COWIterator(Object[] elements, int initialCursor) {
998 cursor = initialCursor;
999 snapshot = elements;
1000 }
You see that the snapshot
points to the array returned by getArray()
and since the array reference returned is volatile, any change on the reference variable is guaranteed to be reflected. (Making the array reference volatile doesn't makes the elements at each index location volatile)
where the change on the array is done inside-
386 public E More ...set(int index, E element) {
387 final ReentrantLock lock = this.lock;
388 lock.lock();
389 try {
390 Object[] elements = getArray();
391 E oldValue = get(elements, index);
392
393 if (oldValue != element) {
394 int len = elements.length;
395 Object[] newElements = Arrays.copyOf(elements, len);
396 newElements[index] = element;
397 setArray(newElements);
398 } else {
399 // Not quite a no-op; ensures volatile write semantics
400 setArray(elements);
401 }
402 return oldValue;
403 } finally {
404 lock.unlock();
405 }
406 }
i.e setArray(newElements);
as seen in the method.
where getArray()
92 final Object[] More ...getArray() {
93 return array;
94 }
& setArray(...)
final void More ...setArray(Object[] a) {
100 array = a;
101 }
are operations on this volatile array
private volatile transient Object[] array;
Clearly, the iterator is returning the array which (if) has been modified, and not the one created at the start of the iteration process.
So, what does the author mean-
return the elements exactly as they were at the time the iterator was created, regardless of subsequent modifications.
Upvotes: 0
Views: 823
Reputation: 185
The code that you've pasted here has these lines (not copied a few lines in between) : Object[] elements = getArray(); Object[] newElements = Arrays.copyOf(elements, len); newElements[index] = element;
Line 1 refers to the existing array that backs this collection. If you try to get an iterator before the collection is updated, then this is the array (referred to as snapshot) iterator gets.
Line 2 is a new array being created by copying (hence the COW) and then new item being added to it as seen in line 3.
This newElements array is then assigned to volatile array reference. Iterator is already referring to older snapshot array, so it doesn't even know about the array being updated.
Hope that explains.
Upvotes: 0
Reputation: 4091
Let's say you instanciate a CopyOnWriteArrayList
, now elements
is referencing an array:
elements -> [0, 1, 2]
Now you create a COWIterator
, snapshot
is referencing the same array:
elements -> [0, 1, 2] <- snapshot
Now you add a new element to the CopyOnWriteArrayList
, a new array is created and the reference is updated to the new array:
snapshot -> [0, 1, 2]
elements -> [0, 1, 3]
So, snapshot
still points to the initial array !
Upvotes: 1
Reputation: 72854
since the array returned is volatile, any change on the reference variable is guaranteed to be reflected.
This has nothing to do with volatile
semantics. The reference to the array is created when the COWIterator
constructor is called (when the iterator is created). Creating a new copy of the array and assigning its reference to the array
field will keep the snapshot
of the iterator pointing to the same old reference.
Upvotes: 4