简体   繁体   中英

List throwing ConcurrentModificationException passed to CompletableFutures

Scenario 1:::
So I have list of params , which is passed to 2 methods which calls web service and gets the data. These to methods just do stream.filter.collect on the list of params to get the needed parameter for rest call. Now I have made the 2 calls parallel using CompletableFutures . Can this throw ConcurrentModifcation exception?

Scenario 2:::
Similar setup as above , just that now one method changes the list of params and adds some objects to it. I know this is throwing Concurrent Modification exp. Should I just make list as copyonWriteArraylist or create new list with deep copy to avoid any further problems.

Scenario #1: Probably not, but your description is too vague to be sure.

Scenario #2: Most absolutely.

The only thing you need for CoModEx to occur is that the list is changed in any way. Be it add , addAll , clear , remove , retainAll , or any other method on List that has the effect of changing the list itself. Even fetching a sublist and changing THAT (as changes to sublist are visible from the 'outer' list that the sublist was created from).

CoModEx, despite the use of the word 'concurrent', has zip squat to do with threads. In fact, messing with a list from two threads simultaneously is one of the few ways you can break things (methods no longer do what their javadoc says they should) without causing a ConcurrentModificationException (will depend on how the race condition goes).

Here is a trivial way to get a CoModEx:

var list = new ArrayList<String>();
list.add("Hello");
list.add("World");
for (String item : list) if (item.equals("Hello")) list.remove(item);

That will throw it. Every time. CoModEx is thrown by iterators (and the for (x:y) constructor will implicitly create iterators, as does x.stream()... , which creates a spliterator, which also does this) when the underlying data structure was changed in any way that is not directly done by the (spl)iterator itself. For example, this is the one way you get to remove things from your own list using an iterator that does not result in CoModEx:

var it = list.iterator();
while (it.hasNext()) {
    if (it.next().startsWith("Hello")) it.remove();
}

Note I'm calling iterator's remove, not list's remove, which would have caused CoModEx: That would change the underlying list (and not via the iterator directly), therefore any operation on an iterator created before the modification will throw CoModEx.

So, this is the flow:

  1. You create an iterator from list , by entering for (String item : list) .
  2. That iterator's hasNext() is invoked to check if the for loop should be entered. It returns true
  3. That iterator's next() is invoked for the first loop; Hello is returned.
  4. Due to the code inside the for loop, list.remove("Hello") is invoked. This 'invalidates' all iterators that were created by this list so far.
  5. the for loop loops, and invokes hasNext() to check if it should loop again.
  6. hasNext realizes that it is invalid, and throws CoModEx.

ArrayList does this by having a counter which is incremented every time anything changes, and all iterators remember the value of the counter when created, and check that the list's counter value is equal to their own. If not, they throw CoModEx. Other list impls can use different mechanisms if they desire. Some go out of their way to actually allow this (such as CopyOnWriteArrayList , which explicitly details how it DOES let you modify itself during iteration).

If multiple threads are involved, all bets are off - those counter writes are not synchronized and therefore may or may not be visible by the threads involved. Don't access the same list from different threads unless you really know what you are doing.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM