Thursday, April 7, 2022

How to Create a thread-safe ConcurrentHashSet in Java 8? [Example]

Until JDK 8, there was no way to create a large, thread-safe, ConcurrentHashSet in Java. The java.util.concurrent package doesn't even have a class called ConcurrentHashSet, but from JDK 8 onwards, you can use the newly added keySet(default value) and newKeySet() methods to create a ConcurrentHashSet backed by ConcurrentHashMap in Java. This is better than old tactical solutions like using a concurrent hash map with dummy value or using the set view of the map, where you cannot add new elements. The Set returned by keySet(defaultValue) and newKeySet() methods of JDK 8 is a proper set, where you can also add new elements along with performing other set operations like contains(), remove() etc.

Though you need to be a little bit careful because these methods are only available in ConcurrentHashMap class and not in the ConcurrentMap interface, so you need to use a ConcurrentHashMap reference variable to hold the reference, or you need to use type casting to cast a ConcurrentHashMap object stored in ConcurrentMap variable.

Btw, this is one of the many useful library enhancements present in JDK 8. If you want to learn more about changes in Java 8, I suggest you take a look at the Java New Features (Java 12, Java 11, Java 10, Java 9 & Java 8) course on Udemy. It provides a nice summary of useful changes not just from Java 8 but also from Java 9 to Java 12 release. 




What are Concurrent Collections in Java?

The Java Concurrency API has concurrent versions of popular Collection classes like the CopyOnArrayList for ArrayList, ConcurrentHahsMap for HashMap, and CopyOnWriteArraySet for HashSet, but there is nothing like ConcurrentHashSet in Java.

Even though CopyOnWriteArraySet is thread-safe it is not suitable for applications where you need a large thread-safe set. It is only used for applications where set sizes stay small and read-only operations vastly outnumber write operations.

So, when you ask Java programmers how to create ConcurrentHashSet without writing their own class, many will say that they can use ConcurrentHashMap with the same values. This is in fact what Java also does to create HashSet. If you have read my article on how HashSet internally works in Java, you may remember that HashSet internally uses HashMap with the same values.

But, the problem with this approach is that you have a map and not a set. You cannot perform set operations on your ConcurrentHashMap with dummy values. You cannot pass it around when some method expects a Set, so it's not very usable.

The other option many Java programmer will mention is that you can get a Set view from ConcurrentHashMap by calling the keySet() method, which in fact return a Set, where you can perform Set operations and pass it around to a method that expects a Set but this approach also has its limitation.

For example,  the Set is backed by ConcurrentHashMap and any change in Map will reflect in Set as well. Another limitation was that you cannot add new elements into this key set, doing so will throw UnsupportedOperationException

If you are not familiar with this exception, I suggest you join The Complete Java MasterClass - Updated, one of the best resources to learn Java by yourself.



Anyway, both of these limitations are now a thing of the past because JDK 8 has added a newKeySet() method which returns a Set backed by a ConcurrentHashMap from the given type where values are Boolean.TRUE.

Unlike the Set view returned from the keySet() method, you can also add new objects into this Set. The method is also overloaded and accepts an initial capacity to prevent resizing of Set.

1. 1 ConcurrentHashSet using newKeySet() in Java 8

Here is a code example to create ConcurrentHashSet in Java 8:
ConcurrentHashMap&ltString, Integer> certificationCosts 
                         = new ConcurrentHashMap<>();
Set<String> concurrentHashSet = certificationCosts.newKeySet();
concurrentHashSet.add("OCEJWCD"); //OK
concurrentHashSet.contains("OCEJWCD"); //OK
concurrentHashSet.remove("OCEJWCD"); //OK


Btw, this is not the only way to create a concurrent, large, thread-safe Set in Java.

You can also use the newly added, overloaded keySet(default value) method to create a ConcurrentHashSet.  This method returns a Set view of the keys in the ConcurrentHashMap, using the given common default value for any additions (i.e., Collection.add() and Collection.addAll(Collection)).

This is of course only use you can use the same value for all elements in the Set, which is Ok in most situations because you don't really care about values in Set. Remember, HashSet is also a HashMap with the same values for all elements, See Java Fundamentals: Collections by Richard Warburton for more details.

How to Create a thread-safe ConcurrentHashSet in Java 8?


1. 2 ConcurrentHashSet using keySet(default value)

Here is the example to obtain a ConcurrentHashSet using keySet(mapped value) method in Java 8:
ConcurrentHashMap&ltString, Integer> certificationCosts = new ConcurrentHashMap<>();
Set<String>concurrentHashSet = certificationCosts.keySet(246); concurrentSet.add("Spring enterprise"); // value will be 246 but no error

you can also perform other Set operations like addAll(), remove(), removeAll(), retainAll(), contains() with this Set.  It is also thread-safe, so can be used in multi-threading Java applications. You can learn more about set-based operations on The Complete Java MasterClass - Updated for Java 11.

ConcurrentHashSet using keySet(default value)




Java Program to Create ConcurrentHashSet from ConcurrentHashMap

Here is our complete Java program to create a large, thread-safe, concurrent hash set in Java 8 using new methods added on java.util.concurrent.ConcurrentHashMap class

import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;

/*
* Java Program to remove key value pair from Map while 
* iteration. 
*/
public class Demo {

public static void main(String[] args) throws Exception {

ConcurrentHashMap certificationCosts = new ConcurrentHashMap<>();
certificationCosts.put("OCAJP", 246);
certificationCosts.put("OCPJP", 246);
certificationCosts.put("Spring Core", 200);
certificationCosts.put("Spring Web", 200);
certificationCosts.put("OCMJEA", 300);


Set concurrentSet = certificationCosts.keySet();

System.out.println("before adding element into concurrent set: " 
                      + concurrentSet);
// concurrentSet.add("OCEJWCD"); // will throw UnsupportedOperationExcetpion
System.out.println("after adding element into concurrent set: " 
                     + concurrentSet);

// creating concurrent hash set in Java 8 using newKeySet() method
Set concurrentHashSet = certificationCosts.newKeySet();


concurrentHashSet.add("OCEJWCD");
concurrentHashSet.contains("OCEJWCD");
concurrentHashSet.remove("OCEJWCD");
System.out.println("after adding element into concurrent HashSet: " 
                      + concurrentSet);

// you can also use keySet(defaultValue) method to add element into Set
concurrentSet = certificationCosts.keySet(246); 
concurrentSet.add("Spring enterprise"); // value will be 246 but no error


}

}

Output
before adding an element into the concurrent set: 
[Spring Web, OCPJP, OCAJP, Spring Core, OCMJEA]
after adding an element into the concurrent set: 
[Spring Web, OCPJP, OCAJP, Spring Core, OCMJEA]
after adding an element into concurrent HashSet: 
[Spring Web, OCPJP, OCAJP, Spring Core, OCMJEA]


You can see that if you try to add new objects into Set returned by the keySet() method of ConcurrentHashMap, it throws UnsupportedOperationExcepiton as shown below:


Exception in thread "main" java.lang.UnsupportedOperationException
at java.util.concurrent.ConcurrentHashMap$KeySetView.add(ConcurrentHashMap.java:4594)
at Demo.main(Demo.java:23)

That's why I have commented that code, but, Set returned by newKeySet() and keySet(mapped value) methods allow you to add new elements into the Set, there is no error there.


By the way, this is not the only way to create a thread-safe Set in Java. Even before Java 8, there is a class called CopyOnWriteArraySet which allows you to create a thread-safe set in Java.

It is similar to CopyOnWriteArrayList and is only suitable for applications where the set size is small. You only do read the only operation because it copies all elements from Set to a new Set every time you write into it. See Java SE 8 for the Really Impatient to learn more about concurrent collections in Java 8.

How to create a thread-safe ConcurrentHashSet in Java 8


Here are some of the important properties of CopyOnWriteArraySet:

1. It is best suited for applications in which set sizes generally stay small, read-only operations vastly outnumber mutative operations, and you need to prevent interference among threads during traversal.

2. It is thread-safe.

3. Mutative operations (add, set, remove, etc.) are expensive since they usually entail copying the entire underlying array.

4. Iterators do not support the mutative removal operation.

5. Traversal via iterators is fast and cannot encounter interference from other threads.

6. Iterators rely on unchanging snapshots of the array when the iterators were constructed. 


That's all about how to create ConcurrentHashSet in Java 8. The JDK 8 API has major features like lambda expression and stream, and these kinds of small changes make your day-to-day coding easier. It's not easy to create a ConcurrentHashSet in Java using the newKeySet() method.

You don't need to use a map like a set with a bogus value or live with the limitation of the set view returned by keySet(), which doesn't allow you to add new elements into the Set.

Further Learning
The Complete Java MasterClass
Java Programming and Software Engineering Fundamentals 
Java 8 for Experienced Developers: Lambdas, Stream API & Beyond


Related Java 8 Tutorials
If you are interested in learning more about the new features of Java 8, here are my earlier articles covering some of the important concepts of Java 8:
  • Top 5 Java 8 Courses for Programmers (courses)
  • 20 Examples of Date and Time in Java 8 (tutorial)
  • 5 Books to Learn Java 8 from Scratch (books)
  • How to join String in Java 8 (example)
  • How to use forEach() method in Java 8 (example)
  • How to use the filter() method in Java 8 (tutorial)
  • 10 examples of Optionals in Java 8? (example)
  • How to use Stream class in Java 8 (tutorial)
  • How to use the peek() method in Java 8 (example)
  • How to convert List to Map in Java 8 (solution)
  • How to format/parse the date with LocalDateTime in Java 8? (tutorial)
  • How to sort the map by keys in Java 8? (example)
  • 10 Java 8 Stream and Functional Programming Interview Questions (answers)
  • How to use the findFirst() method of Stream in Java 8 (example)
  • Java 8 map + filter + collect + stream example (tutorial)

Thanks for reading this article so far. If you like this article, please share it with your friends and colleagues. If you have any questions or feedback then please drop a comment.

P. S. - If you don't mind learning from free resources then you can also check out this list of free Java 8 and Java 9 courses to learn better.

P.  P. S. - If you like to learn from books then Java 8 in Action is the best book to learn both Java 8 features as well as other API enhancements made in JDK 8.

3 comments :

Rawal said...

good explanation

Anonymous said...

ConcurrentHashMap.newKeySet() is static, so you don't need to create an instance of the ConcurrentHashMap before.

Set s = ConcurrentHashMap.newKeySet() is sufficient.

Hua said...

what is the input parameter in keySet(param) doing here?

Post a Comment