31

As per my understanding concurrent collection classes preferred over synchronized collections because the concurrent collection classes don't take a lock on the complete collection object. Instead they take locks on a small segment of the collection object.

But when I checked the add method of CopyOnWriteArrayList, we are acquiring a lock on complete collection object. Then how come CopyOnWriteArrayList is better than a list returned by Collections.synchronizedList? The only difference I see in the add method of CopyOnWriteArrayList is that we are creating copy of that array each time the add method is called.

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();
    }
}
Stuart Marks
  • 127,867
  • 37
  • 205
  • 259
Shashi Shankar
  • 859
  • 2
  • 8
  • 25

3 Answers3

31

As per my understanding concurrent collection classes preferred over synchronized collection because concurrent collection classes don't take lock on complete collection object. Instead it takes lock on small segment of collection object.

This is true for some collections but not all. A map returned by Collections.synchronizedMap locks the entire map around every operation, whereas ConcurrentHashMap locks only one hash bucket for some operations, or it might use a non-blocking algorithm for others.

For other collections, the algorithms in use, and thus the tradeoffs, are different. This is particularly true of lists returned by Collections.synchronizedList compared to CopyOnWriteArrayList. As you noted, both synchronizedList and CopyOnWriteArrayList take a lock on the entire array during write operations. So why are the different?

The difference emerges if you look at other operations, such as iterating over every element of the collection. The documentation for Collections.synchronizedList says,

It is imperative that the user manually synchronize on the returned list when iterating over it:

    List list = Collections.synchronizedList(new ArrayList());
    ...
    synchronized (list) {
        Iterator i = list.iterator(); // Must be in synchronized block
        while (i.hasNext())
            foo(i.next());
    }

Failure to follow this advice may result in non-deterministic behavior.

In other words, iterating over a synchronizedList is not thread-safe unless you do locking manually. Note that when using this technique, all operations by other threads on this list, including iterations, gets, sets, adds, and removals, are blocked. Only one thread at a time can do anything with this collection.

By contrast, the doc for CopyOnWriteArrayList says,

The "snapshot" style iterator method uses a reference to the state of the array at the point that the iterator was created. This array never changes during the lifetime of the iterator, so interference is impossible and the iterator is guaranteed not to throw ConcurrentModificationException. The iterator will not reflect additions, removals, or changes to the list since the iterator was created.

Operations by other threads on this list can proceed concurrently, but the iteration isn't affected by changes made by any other threads. So, even though write operations lock the entire list, CopyOnWriteArrayList still can provide higher throughput than an ordinary synchronizedList. (Provided that there is a high proportion of reads and traversals to writes.)

Stuart Marks
  • 127,867
  • 37
  • 205
  • 259
18

For write (add) operation, CopyOnWriteArrayList uses ReentrantLock and creates a backup copy of the data and the underlying volatile array reference is only updated via setArray(Any read operation on the list during before setArray will return the old data before add).Moreover, CopyOnWriteArrayList provides snapshot fail-safe iterator and doesn't throw ConcurrentModifficationException on write/ add.

But when I checked add method of CopyOnWriteArrayList.class, we are acquiring lock on complete collection object. Then how come CopyOnWriteArrayList is better than synchronizedList. The only difference I see in add method of CopyOnWriteArrayList is we are creating copy of that array each time add method get called.

  1. No, the lock is not on the entire Collection object. As stated above it is a ReentrantLock and it is different from the intrinsic object lock.
  2. The add method will always create a copy of the existing array and do the modification on the copy and then finally update the volatile reference of the array to point to this new array. And that's why we have the name "CopyOnWriteArrayList" - makes copy when you write into it.. This also avoids the ConcurrentModificationException
Yellen
  • 1,785
  • 16
  • 35
  • 2
    Also, CopyOnWriteArrayList cannot be used to modify the list using Iterator, Collections.synchronizedList() can be. – Yellen Mar 11 '15 at 07:31
  • 1
    Correct me if I am wrong, concurrent add will not work in case of CopyOnWriteArrayList because add method is using locking on complete list. Once first thread is done with add operation and releases the lock then only second thread can start with add operation. – Shashi Shankar Mar 11 '15 at 10:18
  • ReentrantLock is different (in a general sense) in that it does not do intrinsic object locking but otherwise it is another mechanism to achieve resource locking in java. Inside the add method of CopyOnWriteArrayList, you can see that the lock is obtained by calling the lock() method of the ReentrantLock. Excerpt from java doc "If the lock is held by another thread then the current thread becomes disabled for thread scheduling purposes". In short, yes, the second thread will wait till the first thread releases the lock. – Yellen Mar 11 '15 at 11:19
13

1) get and other read operation on CopyOnWriteArrayList are not synchronized.

2) CopyOnWriteArrayList's iterator never throws ConcurrentModificationException while Collections.synchronizedList's iterator may throw it.

roottraveller
  • 7,942
  • 7
  • 60
  • 65
Evgeniy Dorofeev
  • 133,369
  • 30
  • 199
  • 275
  • I agreed with both the points mentioned as reads are volatile reads, but want to know is there any difference of add method of synchronizedList and add method of CopyOnWriteArrayList? In both cases we are acquiring lock on complete collection object. – Shashi Shankar Mar 11 '15 at 06:22
  • 1
    CopyOnWriteArrayList creates a copy of the underlying array on each add, it is very expensive – Evgeniy Dorofeev Mar 11 '15 at 07:03
  • CopyOnWriteArrayList is a good when reads is significantly higher than of writes. – Yellen Mar 11 '15 at 07:28