May 25, 2022

How to Synchronize Java ArrayList

This post shows how to synchronize ArrayList in Java and what other thread safe alternatives are available to use.

ArrayList in Java is not thread safe as it is not synchronized by default. If you are using ArrayList in a multi-threaded environment where it is accessed by multiple threads concurrently and it is structurally modified too by even a single thread then it must be synchronized externally. A structural modification is defined as any operation that adds or deletes one or more elements, or explicitly resizes the backing array; merely setting the value of an element is not a structural modification.

Options for thread-safe List

If you want to synchronize ArrayList in Java or looking for a thread-safe alternative of ArrayList then there are following options.

  1. Using Vector class- Vector is synchronized and a thread safe implementation of List. But the problem is all of its methods are synchronized on a single lock so at any time only one thread can use Vector even if it is get() method. That makes Vector very slow to use.
  2. Using Collections.synchronizedList() method- Using this method you can synchronize ArrayList.
  3. Using CopyOnWriteArrayList- Another option is to use CopyOnWriteArrayList which is a thread-safe variant of ArrayList. In CopyOnWriteArrayList all mutative operations (add, set) are implemented by making a fresh copy of the underlying array. Since a new copy is made every time List is changed that is why using CopyOnWriteArrayList is ordinarily too costly. It may be more efficient if you have more traversal operations than mutations and you don't want to synchronize traversals.

Using Collections.synchronizedList() method

Before seeing an example of synchronizing ArrayList in Java using Collections.synchronizedList() method let’s see what may happen if you use ArrayList in a multi-threaded environment with out synchronizing it.

In the Java program four threads are created, each of these thread adds 5 elements to the list. After all the threads are done list size should be 20.

import java.util.ArrayList;
import java.util.List;

public class ListSynchro implements Runnable{    
  private List<Integer> normalList;    
  public ListSynchro(List<Integer> normalList){
    this.normalList = normalList;
  }
    
  public static void main(String[] args) {
    List<Integer> normalList = new ArrayList<Integer>();
    Thread t1 = new Thread(new ListSynchro(normalList));
    Thread t2 = new Thread(new ListSynchro(normalList));
    Thread t3 = new Thread(new ListSynchro(normalList));
    Thread t4 = new Thread(new ListSynchro(normalList));
        
    t1.start();
    t2.start();
    t3.start();
    t4.start();
        
    try {
      t1.join();
      t2.join();
      t3.join();
      t4.join();
    } catch (InterruptedException e) {    
      e.printStackTrace();
    }
    System.out.println("Size of list is " + normalList.size());
  }

  @Override
  public void run() {
    System.out.println("in run method" + Thread.currentThread().getName());
    for(int i = 0; i < 5; i++){
      normalList.add(i);
      try {
        // delay to verify thread interference
        Thread.sleep(500);
      } catch (InterruptedException e) {
        e.printStackTrace();
      }
    }
  }
}
Output
in run methodThread-0
in run methodThread-2
in run methodThread-3
Size of list is 15

As you can see in one of the run size of the list is 15 because of the thread interference.

Here is the same example again where ArrayList is synchronized to make it thread safe.

import java.util.ArrayList;
import java.util.Collections;
import java.util.List;

public class ListSynchro implements Runnable{    
  private List<Integer> normalList;   
  public ListSynchro(List<Integer> normalList){
    this.normalList = normalList;
  }
    
  public static void main(String[] args) {
    // Synchronized ArrayList
    List<Integer> normalList = Collections.synchronizedList(new ArrayList<Integer>());
    Thread t1 = new Thread(new ListSynchro(normalList));
    Thread t2 = new Thread(new ListSynchro(normalList));
    Thread t3 = new Thread(new ListSynchro(normalList));
    Thread t4 = new Thread(new ListSynchro(normalList));
        
    t1.start();
    t2.start();
    t3.start();
    t4.start();
        
    try {
      t1.join();
      t2.join();
      t3.join();
      t4.join();
    } catch (InterruptedException e) {    
      e.printStackTrace();
    }
    System.out.println("Size of list is " + normalList.size());

  }

  @Override
  public void run() {
    System.out.println("in run method" + Thread.currentThread().getName());
    for(int i = 0; i < 5; i++){
      normalList.add(i);
      try {
        // delay to verify thread interference
        Thread.sleep(500);
      } catch (InterruptedException e) {
        e.printStackTrace();
      }
    }
  }
}
Output
in run methodThread-1
in run methodThread-0
in run methodThread-3
in run methodThread-2
Size of list is 20

Iterating a synchronized List

As per Java docs even if you use Collections.synchronizedList() to get a synchronized list, it is imperative that you manually synchronize on the returned list when traversing it via Iterator, Spliterator or Stream:

public class ListItr {
  public static void main(String[] args) {
    List<String> carList = Collections.synchronizedList(new ArrayList<String>());
    carList.add("Audi");
    carList.add("Jaguar");
    carList.add("BMW");
    carList.add("Mini Cooper");
    synchronized (carList) {
      // Must be in synchronized block
      Iterator<String> itr = carList.iterator(); 
      while (itr.hasNext()) {
        System.out.println(itr.next());
      }
    }
  }
}

Using CopyOnWriteArrayList

Another option to have a thread safe list is to use CopyOnWriteArrayList. Since a new copy of the list is made if there is any mutation so there is no thread interference.

Let’s see it using a simple example where a CopyOnWriteArrayList is created and then iterated. While iteration an element is removed using the List’s remove method, it still won’t throw ConcurrentModificationException. In the output you can see iteration is still displaying all the elements as iteration is done on a separate copy of CopyOnWriteArrayList.

import java.util.Iterator;
import java.util.List;
import java.util.concurrent.CopyOnWriteArrayList;

public class CopyItr {
  public static void main(String[] args) {
    List<String> carList = new CopyOnWriteArrayList<String>();
    carList.add("Audi");
    carList.add("Jaguar");
    carList.add("BMW");
    carList.add("Mini Cooper");
    Iterator<String> i = carList.iterator(); 
    while (i.hasNext()){            
      carList.remove("Jaguar");
      System.out.println(i.next()); 
    } 
    System.out.println("List after removal" + carList); 
  }
}
Output
Audi
Jaguar
BMW
Mini Cooper
List after removal[Audi, BMW, Mini Cooper]

That's all for the topic How to Synchronize Java ArrayList. If something is missing or you have something to share about the topic please write a comment.


You may also like

No comments:

Post a Comment