Skip to main content

How to synchronize HashSet in java

How to synchronize HashSet in java?

In this article, we are going to understand how we can synchronize HashSet in Java. As we know, HashSet is not synchronized in java i.e., it is not thread-safe. So we must know how to make a HashSet synchronized so that we can use it in the multi-threaded environment.

Why HashSet is not Synchronized?

First, we should know "Why HashSet is not Synchronized?" before knowing how to make that synchronized. HashSet is a widely used Collection whenever we need to use an implementation of the Set interface. If HashSet would be made as synchronized then it would be much slower as compared to the current implementation of HashSet. Because to make any class synchronized, it needs performance overhead.
Therefore, the developers of HashSet made it as non-synchronized to work faster and provide other ways to make it synchronized.

There are two ways to synchronize HashSet in Java:
  • Using Collections.synchronizedSet() method
  • Using CopyOnWriteArraySet class

How to synchronize HashSet in Java - Using Collections.synchronizedSet() method

The synchronizedSet() is a static method of java.util.Collections utility class. This method takes an argument of Set<T> type and returns an instance of Set<T> type but this returned Set is synchronized.
Syntax
HashSet<T> hashSet = new HashSet<>();
Set<T> synchronizedSet = Collections.synchronizedSet(hashSet);
This returned Set is synchronized i.e., it is a thread-safe variant of the HashSet. All the method of this HashSet is synchronized to each other i.e, At any time, only one thread can operate any operation onto the HashSet.
Also, important to note that if we iterate this synchronized HashSet using iterator then we must iterate within the synchronized block or synchronized method because the iterator provided by the HashSet is not synchronized.

Java Synchronized HashSet Example 1: iterate synchronized HashSet using synchronized block.

import java.util.Collections;
import java.util.HashSet;
import java.util.Iterator;

public class HashSetExample {
    public static void main(String[] args) {
        HashSet<String> cities = new HashSet<>();
        cities.add("Mumbai");
        cities.add("Delhi");
        cities.add("New York");
        cities.add("London");
        cities.add("Beijing");

        // Synchronized HashSet
        Set<String> synchronizedSet = Collections.synchronizedSet(cities);

        // Starting printingThread-1
        printingThread("Thread 1", synchronizedSet);

        // Starting printingThread-1
        printingThread("Thread 2", synchronizedSet);
    }

    public static void printingThread(String threadName, Set<String> synchronizedSet) {
        Runnable runnable = () -> {
            synchronized (synchronizedSet) {
                System.out.println(
                    "-------Synchronized Block of " + Thread.currentThread().getName() + " started----------");
                Iterator<String> iterator = synchronizedSet.iterator();
                while (iterator.hasNext()) {
                    String next = iterator.next();
                    System.out.println(Thread.currentThread().getName() + " : " + next);
                }
                System.out.println(
                    "-------Synchronized Block of " + Thread.currentThread().getName() + " ended------------");
            }
        };
        Thread thread = new Thread(runnable, threadName);
        thread.start();
    }
}
Output
-------Synchronized Block of Thread 1 started----------
Thread 1 : Beijing
Thread 1 : Delhi
Thread 1 : New York
Thread 1 : London
Thread 1 : Mumbai
-------Synchronized Block of Thread 1 ended------------
-------Synchronized Block of Thread 2 started----------
Thread 2 : Beijing
Thread 2 : Delhi
Thread 2 : New York
Thread 2 : London
Thread 2 : Mumbai
-------Synchronized Block of Thread 2 ended------------
As you can see, first the synchronized block of Thread-1 has been started, iterate over the synchronized HashSet and then ended. After that, the synchronized block of Thread-2 was started. This happened due to the presence of synchronized block in the thread if we do not use synchronized block then both the thread iterate over the HashSet parallelly and during that, if any other thread performs any modification onto the HashSet, the ConcurrentModificationException would be raised.

After The Java 8

Java 8 introduced the forEach() method for iterate and as we learn above, all the methods of the synchronized HashSet are synchronized with each other. Therefore, we can use the forEach() method to iterate the synchronized HashSet without using synchronized block and also we don't need to worry about the other thread working on the same HashSet as the forEach() method also synchronized with other methods.
Let's understand this with an example:

Java Synchronized HashSet Example 2: iterate synchronized HashSet using forEach() method.

import java.util.Collections;
import java.util.HashSet;
import java.util.Set;

public class HashSetExample {
    public static void main(String[] args) {
        HashSet<String> cities = new HashSet<>();
        cities.add("Mumbai");
        cities.add("Delhi");
        cities.add("New York");
        cities.add("London");
        cities.add("Beijing");

        // Synchronized HashSet
        Set<String> synchronizedSet = Collections.synchronizedSet(cities);

        // Starting printingThread-1
        printingThread("Thread 1", synchronizedSet);

        // Starting printingThread-1
        printingThread("Thread 2", synchronizedSet);
    }

    public static void printingThread(String threadName, Set<String> synchronizedSet) {
        Runnable runnable = () -> {
            synchronizedSet.forEach(element -> 
                System.out.println(Thread.currentThread().getName() + " : " + element)
            ); 
        };
        Thread thread = new Thread(runnable, threadName);
        thread.start();
    }
}
Output
Thread 1 : Beijing
Thread 1 : Delhi
Thread 1 : New York
Thread 1 : London
Thread 1 : Mumbai
Thread 2 : Beijing
Thread 2 : Delhi
Thread 2 : New York
Thread 2 : London
Thread 2 : Mumbai

How to synchronize HashSet in Java - Using CopyOnWriteArraySet class?

There exist another class in the java.util.concurrent package which is also a thread-safe variant of the HashSet is known as CopyOnWriteArraySet. It is best suited where the read operation onto the HashSet is more as compared to any modification operation.
"The CopyOnWriteArraySet class can also be used to synchronize HashSet in Java, all the modification operation onto the HashSet like add(), addAll(), set(), remove(), etc. are done by making a fresh copy of the underlying array.
It is recommended to use this class where we need a HashSet only or mostly for traversal and read-only operation and Any modification operation is less likely to be performed as it requires making a fresh copy of the underlying Array which is a performance over-head task."
It is better to understand with an example:

Java Synchronized HashSet Example 3: Create synchronized HashSet using CopyOnWriteArraySet and iterate parallelly.

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

public class HashSetExample {
    public static void main(String[] args) {
        HashSet<String> books = new HashSet<>();
        books.add("Learn Java");
        books.add("Coding in Python");
        books.add("Data Structures");
        books.add("Introduction to Database");

        // Synchronized HashSet
        CopyOnWriteArraySet<String> copyOnWriteArraySet = new CopyOnWriteArraySet<>(books);

        // Starting printingThread-1
        printingThread("Thread-1", copyOnWriteArraySet);

        // Starting printingThread-2
        printingThread("Thread-2", copyOnWriteArraySet);
    }

    public static void printingThread(String threadName, CopyOnWriteArraySet<String> synchronizedSet) {
        Runnable runnable = () -> {
            synchronizedSet.forEach(element -> {
                // Add delay of 100 milliseconds. Meanwhile, CPU can switch between threads.
                try {
                    Thread.sleep(100);
                } catch (InterruptedException e) {}

                System.out.println(
                    Thread.currentThread().getName() + " : " + element);
            });
        };
        Thread thread = new Thread(runnable, threadName);
        thread.start();
    }
}
Output
Thread-2 : Coding in Python
Thread-1 : Coding in Python
Thread-1 : Data Structures
Thread-2 : Data Structures
Thread-2 : Introduction to Database
Thread-1 : Introduction to Database
Thread-1 : Learn Java
Thread-2 : Learn Java
As we can see from the output, both threads are iterate over the HashSet parallelly because of the CopyOnWriteArraySet iterator is synchronized among the threads. Also, if any other thread made some modifications to the same CopyOnWriteArraySet, it will not appear in the iterator because to perform any modification, a new fresh copy of the underlying array would be created.

Comments