Show Stopper February 2016

How to synchronize unmodifiable collections

I want to return an unmodifiable view of the class (that maintain a collection of items ) to outside clients .

So to protect concurrent access, I need to wrap the collection in a synchronized wrapper first, then put an unmodifiable wrapper around the version I return to outside threads.

So I wrote the following code and unfortunately it is throwing a ConcurrentModificationException. .

import java.util.*;

public class Test {
public static void main(String[] args) {
    // assume c1 is private, nicely encapsulated in some class
    final Collection col1 = Collections.synchronizedCollection(new ArrayList());
    // this unmodifiable version is public
    final Collection unmodcol1 = Collections.unmodifiableCollection(col1);

    col1.add("a");
    col1.add("b");

    new Thread(new Runnable() {
        public void run() {
            while (true) {
                // no way to synchronize on c1!
                for (Iterator it = unmodcol1 .iterator(); it.hasNext(); it.next())
                    ;
            }
        }
    }).start();

    while (true) {
        col1 .add("c");
        col1 .remove("c");
    }
   }
 }

So my question is How to synchronize unmodifiable collections ?

To add more

When a client who received the collection wants to iterate over its elements

1) it doesn't necessarily know that it's a synchronized collection and

2) even if it does, it can't correctly synchronize on the synchronization wrapper mutex to iterate over its elements. The penalty, as described in Collections.synchronizedCollection, is non-deterministic behaviour.

From my understanding Putting an unmodifiable wrapper on a synchronized collection leaves no access to the mutex that must be held to iterate correctly.

Answers


Gavriel February 2016

You're asking "How to synchronize unmodifiable collections", but actually that's not what you did in your code. You made a syncronized collection unmodifiable. If you 1st make your collection unmodifiable, and then syncronize it, then you'll get what you want.

// you'll need to create the list
final ArrayList list = new ArrayList();
// and add items to it while it's still modifiable:
list.add("a");
list.add("b");
final Collection unmodcol1 = Collections.unmodifiableCollection(list);

final Collection col1 = Collections.synchronizedCollection(unmodcol1);

However the add, remove inside the while will still fail for the same reason.

On the other hand if you created your list and made it unmodifiable, then you might not need to syncronize it at all.


erickson February 2016

If you can ensure that read-only clients of the collection synchronize on the collection, synchronize on that same view in your producer:

/* In the producer... */
Collection<Object> collection = new ArrayList<>();
Collection<Object> tmp = Collections.unmodifiableCollection(collection);
Collection<Object> view = Collections.synchronizedCollection(tmp);
synchronized (view) {
  collection.add("a");
  collection.add("b");
}
/* Give clients access only to "view" ... */

/* Meanwhile, in the client: */
synchronized (view) {
  for (Object o : view) {
    /* Do something with o */
  }
}


Mattias Isegran Bergander February 2016

You need to decide on a few things first.

A. Are users of the returned collection supposed to automatically see updates to it, and when? If so you would need to take care not to (or decide if this is ok) accidently locking it for updates for periods of time. If using synchronized and synchronizing on the returned collection you are effectively allowing the user of the returned collection to lock it for updates for example.

B. Or should they need to call again to get a fresh collection?

Besides, using Collections.synchronizedX won't give you any protection against iterating over it, just individual read and writes. So would require the client to guarantee that it locks during all explicit and implicit iterations. Sounds bad in general, but depends I guess.

Possible solutions:

  1. Return a copy, don't need to wrap it in unmodifiable even. Just lock it while creating it. synchronized (collection) { return new ArrayList(collection); } No further synchronization needed. An example implementation of Option B above.
  2. Like 1 but automatically by the data structure itself, use CopyOnWriteArrayList and return it (wrapped in unmodifiable). Note: This means writes to the collection are expensive. Reads are not. On the other hand even iterating on it is thread safe. No synchronization whatsoever needed. Supports option A above.
  3. Depending on the properties of the data structure you need you could go for a non RandomAccess list like ConcurrentLinkedQueue or ConcurrentLinkedDeque, both allow iterating etc over the data structure without any extra synchroniza


Rastislav Komara February 2016

You may want to use one of concurrent collections. That will give you what you need, if I read your question correctly. Just accept to pay on cost.

List<T> myCollection = new CopyOnWriteArrayList<T>();
List<T> clientsCollection = Collections.unmodifiableList(myCollection);

this way, you will not get CME, as client will always get unmodifiable collection and not interfere with your writes. However, price is rather high.

Post Status

Asked in February 2016
Viewed 1,884 times
Voted 4
Answered 4 times

Search




Leave an answer