March 10, 2022

How to Synchronize Java HashSet

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

HashSet is not thread safe

HashSet in Java is not thread safe as it is not synchronized by default. If you are using HashSet in a multi-threaded environment where it is accessed by multiple threads concurrently and 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 Set

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

  1. Use Collections.synchronizedSet() to synchronize Set- This method returns a synchronized (thread-safe) set backed by the specified set.
  2. Using CopyOnWriteArraySet- Another option is to use CopyOnWriteArraySet from the java.util.concurrent package which is the thread safe implementation of the Set. CopyOnWriteArraySet uses an internal CopyOnWriteArrayList for all of its operations. For all mutative operations (add, set, remove, etc.) a new copy is created that is why using it is expensive.

Using Collections.synchronizedSet()

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

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

public class SetSynchro implements Runnable{
  private Set<String> numSet;
  public SetSynchro(Set<String> numSet){
    this.numSet = numSet;
  }
  
  public static void main(String[] args) {
    Set<String> numSet = new HashSet<String>();
    /// 4 threads
    Thread t1 = new Thread(new SetSynchro(numSet));
    Thread t2 = new Thread(new SetSynchro(numSet));
    Thread t3 = new Thread(new SetSynchro(numSet));
    Thread t4 = new Thread(new SetSynchro(numSet));
        
    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 Set is " + numSet.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
      numSet.add(i + str);
      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 Set is 19

In one of the run size was 19, in another run 18 and sometimes even 20, so you can see that thread interference is making the behavior unpredictable. So we’ll synchronize the HashSet using the same example.

public class SetSynchro implements Runnable{
  private Set<String> numSet;

  public SetSynchro(Set<String> numSet){
    this.numSet = numSet;
  }

  public static void main(String[] args) {
    // Synchronized Set
    Set<String> numSet = Collections.synchronizedSet(new HashSet<String>());
    /// 4 threads
    Thread t1 = new Thread(new SetSynchro(numSet));
    Thread t2 = new Thread(new SetSynchro(numSet));
    Thread t3 = new Thread(new SetSynchro(numSet));
    Thread t4 = new Thread(new SetSynchro(numSet));
    
    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 Set is " + numSet.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
      numSet.add(i + str);
      try {
        // delay to verify thread interference
        Thread.sleep(500);
      } catch (InterruptedException e) {
        e.printStackTrace();
      }
    }
  }
}
Output
in run methodThread-3
in run methodThread-2
in run methodThread-1
in run methodThread-0
Size of Set is 20

Now every time size of HashSet is 20.

Using CopyOnWriteArraySet

Another option to have a thread safe Set is to use CopyOnWriteArraySet. Let’s see it using a simple example where a CopyOnWriteArraySet is created and then iterated. While iteration an element is removed using the Set’s remove method, it still won’t throw ConcurrentModificationException. In the output you can see iteration is displaying all the elements as iteration is done on a separate copy of CopyOnWriteArraySet.

import java.util.Iterator;
import java.util.Set;
import java.util.concurrent.CopyOnWriteArraySet;

public class SetItr {

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

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