January 21, 2023

How to Synchronize Java HashMap

This post shows how to synchronize HashMap in Java and the HashMap’s thread safe alternative which can be used.

HashMap is not thread safe

If HashMap is accessed by multiple threads concurrently and at least one of the threads modifies the map structurally, you must synchronize HashMap externally. Note that structural modification is any operation that adds or deletes one or more mappings; merely changing the value associated with a key that an instance already contains is not a structural modification.

Options for thread-safe Map

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

  1. Use Collections.synchronizedMap() to synchronize Map- This method returns a synchronized (thread-safe) map backed by the specified map. In case you are synchronizing a TreeMap which is a sorted Map you can use synchronizedSortedMap() method
  2. Using ConcurrentHashMap- Another option is to use ConcurrentHashMap from the java.util.concurrent package. All of its operations are thread-safe and it provides better concurrency. ConcurrentHashMap provides better performance by using separate locks for separate buckets rather than synchronizing the whole Map on a single lock.

Using Collections.synchronizedMap()

You can synchronize your HashMap by using Collections.synchronizedMap() method. First we’ll see an example what happens if HashMap is used in a multi-threaded environment without synchronizing it.

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

import java.util.HashMap;
import java.util.Map;

public class MapSynchro implements Runnable{
  private Map<String, String> testMap;
  public MapSynchro(Map<String, String> testMap){
    this.testMap = testMap;
  }

  public static void main(String[] args) {
    // Synchronized Map
    Map<String, String> testMap = new HashMap<String, String>();
    /// 4 threads
    Thread t1 = new Thread(new MapSynchro(testMap));
    Thread t2 = new Thread(new MapSynchro(testMap));
    Thread t3 = new Thread(new MapSynchro(testMap));
    Thread t4 = new Thread(new MapSynchro(testMap));
    
    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 Map is " + testMap.size());
  }
    
  @Override
  public void run() {
    System.out.println("in run method" + Thread.currentThread().getName());
    String str = Thread.currentThread().getName();
    for(int i = 0; i < 5; i++){
      // adding thread name to make element unique
      testMap.put(str+i, str+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-2
in run methodThread-3
Size of Map is 19

In different run I got different HashMap sizes, 17, 18, 19 and 20 because of the thread interference.

Now, if we synchronize the HashMap in the same Java example using Collections.synchronizedMap() method.

public class MapSynchro implements Runnable{
  private Map<String, String> testMap;
  public MapSynchro(Map<String, String> testMap){
    this.testMap = testMap;
  }

  public static void main(String[] args) {
    // Synchronized Map
    Map<String, String> testMap = Collections.synchronizedMap(new HashMap<String, String>());
    /// 4 threads
    Thread t1 = new Thread(new MapSynchro(testMap));
    Thread t2 = new Thread(new MapSynchro(testMap));
    Thread t3 = new Thread(new MapSynchro(testMap));
    Thread t4 = new Thread(new MapSynchro(testMap));
    
    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 Map is " + testMap.size());
  }
  @Override
  public void run() {
    System.out.println("in run method" + Thread.currentThread().getName());
    String str = Thread.currentThread().getName();
    for(int i = 0; i < 5; i++){
      // adding thread name to make element unique
      testMap.put(str+i, str+i);
      try {
        // delay to verify thread interference
        Thread.sleep(500);
      } catch (InterruptedException e) {
        e.printStackTrace();
      }
    }
  }
}
Output
in run methodThread-2
in run methodThread-0
in run methodThread-3
in run methodThread-1
Size of Map is 20

Now the size of Map is 20 every time.

Using ConcurrentHashMap

Other than synchronizing HashMap, another option to have a thread safe HashMap is to use ConcurrentHashMap in Java. Let’s see the same example as above using ConcurrentHashMap.

import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;

public class MapSynchro implements Runnable{
  private Map<String, String> testMap;
  public MapSynchro(Map<String, String> testMap){
    this.testMap = testMap;
  }

  public static void main(String[] args) {
    // Synchronized Map
    Map<String, String> testMap = new ConcurrentHashMap<String, String>();
    /// 4 threads
    Thread t1 = new Thread(new MapSynchro(testMap));
    Thread t2 = new Thread(new MapSynchro(testMap));
    Thread t3 = new Thread(new MapSynchro(testMap));
    Thread t4 = new Thread(new MapSynchro(testMap));
    
    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 Map is " + testMap.size());
  }
  
  @Override
  public void run() {
    System.out.println("in run method" + Thread.currentThread().getName());
    String str = Thread.currentThread().getName();
    for(int i = 0; i < 5; i++){
      // adding thread name to make element unique
      testMap.put(str+i, str+i);
      try {
        // delay to verify thread interference
        Thread.sleep(500);
      } catch (InterruptedException e) {
        e.printStackTrace();
      }
    }
  }
}
Output
in run methodThread-2
in run methodThread-0
in run methodThread-3
in run methodThread-1
Size of Map is 20

That's all for the topic How to Synchronize Java HashMap. 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