Sunday, January 16, 2022

How and Why to Synchronize ArrayList in Java

ArrayList is one of the most used Collections and why shouldn't it be? It's easy to use, has many implemented methods to provide all the important functionality and it is fast. But that fast performance comes at a cost, ArrayList is not synchronized. What does that mean? That means sharing an instance of ArrayList among many threads where those threads are modifying (by adding or removing the values) the collection may result in unpredictable behavior.

So in this post we'll see why we may need to synchronize an ArrayList and how to synchronize an ArrayList in Java.


Why do we need to synchronize an ArrayList

First let's see why do we need to synchronize an ArrayList in Java. To understand that an example is given below where an ArrayList instance is shared among three threads and each thread is trying to insert ten elements in the ArrayList.

Expected output of the example is: Size of ArrayList should be 30 and on looping the list I should get values 0..9 three times. As I said it may give unpredictable result so let's see what happens when we run it.

public class SynchroProblem implements Runnable{
  private List<Integer> numList;

  //Constructor
  public SynchroProblem(List<Integer> numList){
    this.numList = numList;
  }
  @Override
  public void run() {
    System.out.println("in run method");
    for(int i = 0; i < 10; i++){
      numList.add(i);
      try {
        // introducing some delay
        Thread.sleep(50);
      } catch (InterruptedException e) {
        e.printStackTrace();
      }
    }
  }
    
  public static void main(String[] args) {
    List<Integer> numList = new ArrayList<Integer>();
    // Creating three threads
    Thread t1 = new Thread(new SynchroProblem(numList));
    Thread t2 = new Thread(new SynchroProblem(numList));
    Thread t3 = new Thread(new SynchroProblem(numList));
    t1.start();
    t2.start();
    t3.start();
    try {
      t1.join();
      t2.join();
      t3.join();
    } catch (InterruptedException e) {    
      e.printStackTrace();
    }
    System.out.println("Size of list is " + numList.size());
    for(Integer i : numList){
      System.out.println("num - " + i);
    }    
  }
}

Output

in run method
in run method
in run method
Size of list is 27
num - null
num - 0
num - 0
num - 1
num - 1
num - 1
num - 2
num - 2
num - 2
num - 3
num - null
num - 3
num - 4
num - 4
num - 4
num - 5
num - 5
num - 5
num - 6
num - 6
num - 7
num - 7
num - 7
num - 8
num - 9
num - 9
num - 9

It can be seen from the output that size is 27 (not 30) and two of the values are NULL where as 6 is inserted only twice and 8 only one time. So you can see the effect of using an arraylist which is not thread-safe in multi-threaded environment. Note that if you run this program on your system you may get different result. Well it is supposed to be unpredictable!

Options for synchronizing an ArrayList in Java

Now when we know ArrayList is not synchronized and sharing its instance among many threads may give unpredictable result, we can focus on the other part; how to synchronize an ArrayList in Java. The options we have are as following-

  • We can of course use a Vector which is synchronized.
  • Collections class also provide a method synchronizedList(), which returns a synchronized (thread-safe) list backed by the specified list.
  • Another alternative is CopyOnWriteArrayList which is part of the java.util.concurrent Package.

Let's see the same example used above with one change, now Collections.synchronizedList is used to synchronize ArrayList.

public class SynchroProblem implements Runnable{
  private List<Integer> numList;

  //Constructor
  public SynchroProblem(List<Integer> numList){
    this.numList = numList;
  }
  @Override
  public void run() {
    System.out.println("in run method");
    for(int i = 0; i < 10; i++){
      numList.add(i);
      try {
        // introducing some delay
        Thread.sleep(50);
      } catch (InterruptedException e) {
        e.printStackTrace();
      }
    }
  }
    
  public static void main(String[] args) {
    //List<Integer> numList = new Vector<Integer>();
    // Synchronizing the list
    List<Integer> numList = Collections.synchronizedList(new ArrayList<Integer>());
    // Creating three threads
    Thread t1 = new Thread(new SynchroProblem(numList));
    Thread t2 = new Thread(new SynchroProblem(numList));
    Thread t3 = new Thread(new SynchroProblem(numList));
    t1.start();
    t2.start();
    t3.start();
    try {
      t1.join();
      t2.join();
      t3.join();
    } catch (InterruptedException e) {    
      e.printStackTrace();
    }
    System.out.println("Size of list is " + numList.size());
    for(Integer i : numList){
      System.out.println("num - " + i);
    }    
  }
}

Output

Size of list is 30

Now every time I get the size of the list as 30. Also elements are displayed properly 0..9 three times (I have omitted the display here).
We can also use the Vector class. It is already there in the code just uncomment the line with the vector and comment the Collections.synchronized line to see how it works with vector class. Note that Vector class is also retrofitted to use List interface that is why List reference is used to hold the Vector class.

Iterating a list in multi-threaded environment

When iterating an arraylist in a multi-threaded environment while one thread is iterating over a list there's a chance that another thread may remove a value which will disrupt the serial access.

In that case also we need to synchronize the list, In order to guarantee serial access, it is critical that all access to the backing list is accomplished through the returned list. It is imperative that the user manually synchronize on the returned list when iterating over it. Failure to follow this advice may result in non-deterministic behavior.

Reference: https://docs.oracle.com/javase/10/docs/api/java/util/Collections.html#synchronizedList(java.util.List)

List list = Collections.synchronizedList(new ArrayList());
...
synchronized (list) {
  Iterator i = list.iterator(); // Must be in synchronized block
  while (i.hasNext())
    foo(i.next());
}

That's all for this topic How and Why to Synchronize ArrayList in Java. If you have any doubt or any suggestions to make please drop a comment. Thanks!


Related Topics

  1. How ArrayList Works Internally in Java
  2. How to Sort ArrayList of Custom Objects in Java
  3. How to Remove Elements From an ArrayList in Java
  4. Synchronization in Java - Synchronized Method And Block
  5. Java Collections Interview Questions And Answers

You may also like-

  1. CopyOnWriteArrayList in Java With Examples
  2. Fail-Fast Vs Fail-Safe Iterator in Java
  3. equals() And hashCode() Methods in Java
  4. strictfp in Java
  5. static import in Java
  6. Class in Java
  7. Marker interface in Java
  8. Java BlockingQueue With Examples

1 comment: