Keeping ArrayLists safe across threads in Java

Arnav Gupta
Coding Blocks
Published in
2 min readMar 13, 2019

When working on bigger projects, and optimizing for performance and scale, we inevitably have to deal with threads. Threads allow parallel execution. Java has a Thread class, which can be used to achieve this.

Note that threads we create in Java are software threads. It does not necessarily mean each thread we create gets a dedicated CPU thread to run on. I am not delving much into this, but if you have an academic interest you should read about how hardware and software threads work.

Welcome to ConcurrentModificationException

Let us start with a contrived example, of adding items to an ArrayList over three threads.

When this starts running we start getting things like this printed

Yes, the order of printing might get shuffled like this too, because they are across 3 threads.

But soon enough we hit this snag

Exception in thread "t3" java.util.ConcurrentModificationException at java.util.ArrayList$Itr.checkForComodification(ArrayList.java:901) at java.util.ArrayList$Itr.next(ArrayList.java:851) at java.util.AbstractCollection.toString(AbstractCollection.java:461) at in.championswimmer.Main.lambda$main$0(Main.java:16)

This happens because the ArrayList class of Java has modCount and expectedModCount variables it uses to track the iterator and size of the list. Which if not equal, leads to this exception.
Eventually, 2 threads will crash like this, and the remaining one will continue till the end.

Possible Solutions

Can we synchronized it ?

Okay, one naive solution that people try is to wrap the problematic code into a synchronized block.
If we wrap only the ArrayList#add() operation, that won't solve our problem.

synchronized (numList) { 
numList.add(r.nextInt(50));
}

Just wrap the add line in synchronized block and run and check what happens.

Okay, how about synchronized around the whole while loop ?

synchronized (numList) { 
while (numList.size() < 100) {
numList.add(r.nextInt(50));
System.out.print("Thread = " + Thread.currentThread().getName());
System.out.println(numList.toString());
}
}

If you try the above code, you’ll see you completely loose the benefit of multi-threading. Only one thread will run from size 0 to 50 and fill all the items in the ArrayList.

The CopyOnWriteArrayList

Another solution is CopyOnWriteArrayList, a special class in Java that makes a copy of the entire array on each write operation, and thus makes write operations on the List thread-safe.

This should work perfectly, albeit at a cost of increased memory usage and more GC load (garbage collection), because every iteration creates a new array, and dumps the old one for GC to come and clean up behind it.

Using Collections.synchronizedList()

One more solutions is to use Collections.synchronizedList() which creates a synchronized and thread List.

Do keep in mind that when performing list.iterator().next() we have to manually put that in a synchronized block, if we are ever using the iterator of a SynchronizedList.

A SynchronizedList uses an internal mutex to ensure thread safety, and thus we can achieve thread safe list addition without the upfront memory spike we get with a CopyOnWriteArray.

Originally published at blog.codingblocks.com on March 13, 2019.

--

--

Arnav Gupta
Coding Blocks

Swimmer, Coder, Poet, Engineer, Entrepreneur. Co founder of Coding Blocks. Mobile Platform at Zomato