0

From Javadocs for Collections class:

 public static <T> List<T> synchronizedList(List<T> list)

Returns a synchronized (thread-safe) list backed by the specified list. In order to guarantee serial access, it is critical that all access to the "backing list" is accomplished through the returned list.

Do I understand correctly, that "backing list" means method argument (list), so below the line of code List<String> mySyncList = Collections.synchronizedList(origList); I shall never do anything like origList.anyMethodCall(), including: origList.add("blabla"), or origList.remove("blabla"), origList.set(1, "blabla")? Though it compiles! Can I access "backing list" in a way not making structural modifications (origList.contains("blabla"))? I guess, I can, but it is also "access to the backing list"! And one is supposed to follow official Oracle docs...

Is that right, that problem arises ONLY when origList STRUCTURALLY MODIFIED AFTER I obtained iterator from mySyncList and BEFORE I finished using this iterator?

If so, am I right, that if thread-3 structurally modifies origList, then iterating mySyncList in any other thread gives ConcurrentModificationException, but there are absolutely no problems so far as either thread-3 modifies origList non-structurally (contains()), or thread-3 modifies origList structurally but there is no iteration in mySyncList (mySyncList.add, mySyncList.remove, ...)?

public static void main(String[] args) {
        List<String> origList = new ArrayList<>();
        origList.add("one");
        origList.add("two");
        origList.add("three");

        List<String> mySyncList = Collections.synchronizedList(origList);
        origList.add("blabla"); // prohibited by Oracle ??? :)))
        origList.add("blabla"); // prohibited by Oracle ??? :)))
        origList.add("blabla"); // prohibited by Oracle ??? :)))
        origList.add("blabla"); // prohibited by Oracle ??? :)))
        // now use mySyncList
        System.out.println(mySyncList); // no problem so far

        // P.S.: Maybe problem arises ONLY when origList STRUCTURALLY MODIFIED
        // AFTER I obtained iterator from mySyncList and BEFORE I finished
        // using this iterator? If so, such wording would be much preferable in
        // official docs!
    }

P.S. My question is different, and I do understand that:

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

SO reseach did't yield answer, I diligently reviewed all the following:

THANKS TO GRAY ACCEPTED ANSWER BELOW I FINALLY CAME TO THE FOLLOWING CONCLUSION (my brief outline of Gray's answer):

After writing "List mySyncList = Collections.synchronizedList(origList)" it is recommended to write in next line "origList = null". In practice, guru never use origList anywhere later in code (in any thread, in entire prog).

Theoretically there is no problem to use origList later provided that it is not modified structurally (add, remove not called), but no one can safely guarantee only non-structural accesses in practice.

The ideology behind that is this: you converted origList into thread-safe mySyncList, and now use only mySyncList for multithreaded purposes and forget about origList !!!

Community
  • 1
  • 1

1 Answers1

0

[After calling synchronizedList(...)] I shall never do anything like origList.anyMethodCall(), including: origList.add("blabla"), or origList.remove("blabla"), origList.set(1, "blabla")?

That's correct. Whenever you have multiple threads updating a list, you have to make sure that all threads are dealing with it in a synchronized manner. If one thread is accessing this list through the Collections.synchronizedList(...) wrapper and another thread is not then you can easily have data corruption issues that result in infinite loops or random runtime exceptions.

In general, once you have the synchronized wrapped version of the list I'd set the origList to be null. There is no point in using it anymore. I've written and reviewed a large amount of threaded code and never seen anyone use the original list once it is wrapped. It really feels like premature optimization and a major hack to keep using the original list. If you are really worried about performance then I'd switch to using ConcurrentLinkedQueue or ConcurrentSkipList.

Can I access "backing list" in a way not making structural modifications (origList.contains("blabla"))?

Yes, BUT no threads can make structural modifications. If a thread using the synchronized version adds an entry and then the other thread accesses the non-synchronized version then the same race conditions that can result in an partially synchronized version of the list causing problems.

Is that right, that problem arises ONLY when origList STRUCTURALLY MODIFIED AFTER I obtained iterator from mySyncList and BEFORE I finished using this iterator?

Yes, that should be ok as long as you can guarantee that fact. But again, this feel like a hack. If someone changes the behavior of another thread or if the timing is changed in the future, your code will start breaking without any warning.

For others, the problem with the synchronized list wrapper, as opposed to a fully concurrent collection like the ConcurrentSkipList, is that an iterator is multiple operations. To quote from the javadocs for Collections.synchronizedList(...):

It is imperative that the user manually synchronize on the returned list when iterating over it. [removed sample code] Failure to follow this advice may result in non-deterministic behavior.

The synchronized wrapper protects the underlying list during each method call but all bets are off if you are using an iterator to walk your list at the same time another thread is modifying it because multiple method calls are made. See the sample code in the javadocs for more info.

there are absolutely no problems so far as either thread-3 modifies origList non-structurally (contains()), or thread-3 modifies origList structurally but there is no iteration in mySyncList

As long as both threads are using the synchronized wrapper then yes, there should be no problems.

origList.add("blabla"); // prohibited by Oracle ??? :)))

We aren't talking about "prohibited" which sounds like a violation of the language definition. We are talking about properly reentrant code working with properly synchronized collections. With reentrant code, the devil is in the details.

Gray
  • 115,027
  • 24
  • 293
  • 354