12

I have a method similar to the one below:

public void addSubjectsToCategory() {
    final List<Subject> subjectsList = new ArrayList<>(getSubjectList());
    for (final Iterator<Subject> subjectIterator =
            subjectsList.iterator(); subjectIterator.hasNext();) {
         addToCategory(subjectIterator.next().getId());
    } 
}

When this runs concurrently for the same user (another instance), sometimes it throws NoSuchElementException. As per my understanding, sometimes subjectIterator.next() get executed when there are no elements in the list. This occurs when being accessed only. Will method synchronization solve this issue?

The stack trace is:

java.util.NoSuchElementException: null
at java.util.ArrayList$Itr.next(Unknown Source)
at org.cmos.student.subject.category.CategoryManager.addSubjectsToCategory(CategoryManager.java:221)

This stack trace fails at the addToCategory(subjectIterator.next().getId()); line.

17
  • 1
    How about adding the above list to Collections.synchronizedList() and then do the iteration? Commented Jul 25, 2018 at 14:08
  • 2
    Yes, synchronized list is a better approach. Commented Jul 25, 2018 at 14:09
  • 3
    I think problem could be with copy of the list at the beginning. For statement is fine; if list is empty, it will never be inside, because hasNext() is executed before each loop, even before the first one. Commented Jul 25, 2018 at 14:10
  • 1
    With the exception of the external call to getSubjectList there is nothing in this code that needs to be synchronized or should fail due to concurrency: you are iterating via a local var which you create once per executing thread. Your problem most likely lies with getSubjectList() or with iterating over the result of that call if that result is not a newly constructed collection but an iterable. Commented Jul 25, 2018 at 14:14
  • 3
    Randomly adding synchonized keywords is not the solution. You need to understand where and why it fails first. Please provide the stack trace of the exception. Commented Jul 25, 2018 at 14:41

7 Answers 7

9
+50

The basic rule of iterators is that underlying collection must not be modified while the iterator is being used.

If you have a single thread, there seems to be nothing wrong with this code as long as getSubjectsList() does not return null OR addToCategory() or getId() have some strange side-effects that would modify the subjectsList. Note, however, that you could rewrite the for-loop somewhat nicer (for(Subject subject: subjectsList) ...).

Judging by your code, my best guess is that you have another thread which is modifying subjectsList somewhere else. If this is the case, using a SynchronizedList will probably not solve your problem. As far as I know, synchronization only applies to List methods such as add(), remove() etc., and does not lock a collection during iteration.

In this case, adding synchronized to the method will not help either, because the other thread is doing its nasty stuff elsewhere. If these assumptions are true, your easiest and safest way is to make a separate synchronization object (i.e. Object lock = new Object()) and then put synchronized (lock) { ... } around this for loop as well as any other place in your program that modifies the collection. This will prevent the other thread from doing any modifications while this thread is iterating, and vice versa.

Sign up to request clarification or add additional context in comments.

11 Comments

Thanks Jurez for your time. subjectsList does not get modified by other threads. It just holds the info of subjects. Does it matter since a new list is created for iteration instead of iterating through subjectsList directly?
@Kevin Then, what does "concurrent mode" mean exactly?
However, there is a different possibility - I've looked at ArrayList.Itr.next() and the only way it can produce NoSuchElementException is when it thinks its size is actually different from the underlying array. This could imply a problem during the copying (new ArrayList<>(getSubjectList())) if the original list is modified while it is being copied.
As for the synchronization on separate object: it is exactly the same as using synchronized method - except you have more control over it. Synchronized method is the same as putting synchronized(this) around entire method body.
Yes. Copying is done in two steps - one is copying the array and second is copying the size. The larger the array, the longer the time and the higher the probability. If you suspect this is the scenario, you actually need to synchronize on original list in the same way as proposed above - a single lock that covers copying (first line of your method) and other modifications to original list elsewhere in your code.
|
3
subjectIterator.hasNext();) {

--- Imagine a thread switch occurs here, at this point, between the call to hasNext() and next() methods.

addToCategory(subjectIterator.next().getId());

What could happen is the following, assuming you are at the last element in the list:

  • thread A calls hasNext(), the result is true;
  • thread switch occurs to thread B;
  • thread B calls hasNext(), the result is also true;
  • thread B calls next() and gets the next element from the list; now the list is empty because it was the last one;
  • thread switch occurs back to thread A;
  • thread A is already inside the body of the for loop, because this is where it was interrupted, it already called hasNext earlier, which was true;
  • so thread A calls next(), which fails now with an exception, because there are no more elements in the list.

So what you have to do in such situations, is to make the operations hasNext and next behave in an atomic way, without thread switches occurring in between. A simple synchronization on the list solves, indeed, the problem:

public void addSubjectsToCategory() {
    final ArrayBlockingQueue<Subject> subjectsList = new ArrayBlockingQueue(getSubjectList());
    synchronized (subjectsList) {
        for (final Iterator<Subject> subjectIterator =
            subjectsList.iterator(); subjectIterator.hasNext();) {
                addToCategory(subjectIterator.next().getId());
        }
    }
}

Note, however, that there may be performance implications with this approach. No other thread will be able to read or write from/to the same list until the iteration is over (but this is what you want). To solve this, you may want to move the synchronization inside the loop, just around hasNext and next. Or you may want to use more sophisticated synchronization mechanisms, such as read-write locks.

Comments

3

It sounds like another thread is calling the method and grabbing the last element while another thread is about to get the next. So when the other thread finishes and comes back to the paused thread there is nothing left. I suggest using an ArrayBlockingQueue instead of a list. This will block threads when one is already iterating.

public void addSubjectsToCategory() {
    final ArrayBlockingQueue<Subject> subjectsList = new ArrayBlockingQueue(getSubjectList());
    for (final Iterator<Subject> subjectIterator =
         subjectsList.iterator(); subjectIterator.hasNext();) {
        addToCategory(subjectIterator.next().getId());
    }
}

There is a bit of a wrinkle that you may have to sort out. The ArrayBlockingQueue will block if it is empty or full and wait for a thread to either insert something or take something out, respectively, before it will unblock and allow other threads to access.

2 Comments

Thanks @Jim , Yes that is what exactly happens. Will try this out and come back..
I'm not sure if this would explain the problem. If another thread enters addSubjectsToCategory, it will create its own copy of list - that should not interfere with the original thread's iterator.
2

You can use Collections.synchronizedList(list) if all you need is a simple invocation Sycnchronization. But do note that the iterator that you use must be inside the Synchronized block.

Comments

2

As I get you are adding elements to a list which might be under reading process. Imagine the list is empty and your other thread is reading it. These kinds of problems might lead into your problem. You could never be sure that an element is written to your list which you are trying to read , in this approach.

Comments

2

I was surprised not to see an answer involving the use of a CopyOnWriteArrayList or Guava's ImmutableList so I thought that I would add such an answer here.

Firstly, if your use case is such that you only have a few additions relative to many reads, consider using the CopyOnWriteArrayList to solve the concurrent list traversal problem. Method synchronization could solve your issue, but CopyOnWriteArrayList will likely have better performance if the number of concurrent accesses "vastly" exceeds the number of writes, as per that class's Javadoc.

Secondly, if your use case is such that you can add everything to your list upfront in a single-threaded manner and only then do you need iterate across it concurrently, then consider Guava's ImmutableList class. You accomplish this by first using a standard ArrayList or a LinkedList or a builder for your ImmutableList. Once your single-threaded data entry is complete, then you instantiate your ImmutableList using either ImmutableList.copyOf() or ImmutableList.build(). If your use case will allow for this write/read pattern, this will probably be your most performant option.

Hope that helps.

1 Comment

Thank you @entpnerd
0

I would like to make a suggestion that would probably solve your problem, considering that this is a concurrency issue.

If making the method addSubjectsToCategory() synchronized solves your problem, then you have located where your concurrency issue is. It is important to locate where the problem occurs, otherwise the information you provided is useless to us, we can't help you.

IF using synchronized in your method solves your problem, then consider this answer as educational or as a more elegant solution. Otherwise, share the code where you implement your threading environment, so we can have a look.

public synchronized void addSubjectsToCategory(List subjectsList){
  Iterator iterator = subjectsList.iterator();
  while(iterator.hasNext())
    addToCategory(iterator.next().getId());
}

or

//This semaphore should be used by all threads. Be careful not to create a
//different semaphore each time.
public static Semaphore mutex = new Semaphore(1);

public void addSubjectsToCategory(List subjectsList){
  Iterator<Subject> iterator = subjectsList.iterator();
  mutex.acquire();
  while(iterator.hasNext())
    addToCategory(iterator.next().getId());
  mutex.release();
}

Synchronized is clean, tidy and elegant. You have a really small method and creating locks, imho is unnecessary. Synchronized means that only 1 thread will be able to enter the method at a time. Which means, you should use it only if you want 1 thread active each time.

If you actually need parallel execution, then your problem is not thread-related, but has something to do with the rest of your code, which we can not see.

3 Comments

This won't solve any problem as the concurrent access isn't from this method but from somewhere else. As other answers point out, adding random concurrency constructs in one place without understanding the problem is like to solve exactly nothing.
Based on what the OP has said, his threads access this method. I provided an answer, based on the information that the OP provided. I don't understand what your point is. What do you mean the concurrent access isn't from this method? If the program accesses this method in such way, that it creates a race condition, then a method needs to become thread-safe. Adding random comments in one place without understanding the concept, will help for exactly nothing
Thank you gentlemen

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.