How to create a thread-safe ConcurrentHashSet in Java 8?
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 newly added keySet(default value) and newKeySet() method to create a ConcurrentHashSet backed by ConcurrentHashMap. Unlike tactical solutions like using 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() method of JDK 8 is a proper set, where you can also add new elements along with performing other set operations e.g. contains(), remove() etc. Though you need to be careful that these methods are only available in ConcurrentHashMap class and not in ConcurrentMap interface, so you need to use a ConcurrentHashMap variable to hold the reference, or you need to use type casting to cast a ConcurrentHashMap object stored in ConcurrentMAp variable.
The Java Concurrency API has concurrent versions of popular Collection classes e.g. CopyOnArrayList for ArrayList, ConcurrentHahsMap for HashMap and CopyOnWriteArraySet for HashSet, but there was nothing like ConcurrentHashSet in Java. Even though, CopyOnWriteArraySet is thread-safe it is not suitable for application where you need a large thread-safe set. It is only used for application where set sizes stay small and read-only operations vastly outnumber write operations.
So, when you ask Java programmers about how to create ConcurrentHashSet without writing their own class, many will say that they can use ConcurrentHashMap with bogus values. This is in fact what Java also does because if you know HashSet internally uses HashMap with same values.
But, the problem with this approach is that you have a map and not 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 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 which expects a Set but this approach also has its limitation e.g. 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. See
Java 8 in Action to learn more about it.
Both of these limitations are now thing of past because JDK 8 has added newKeySet() method which returns a Set backed by a ConcurrentHashMap from the given type where values are Boolean.TRUE. Unlike 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.
Here is a code example to create ConcurrentHashSet in Java 8:
ConcurrentHashMap certificationCosts = new ConcurrentHashMap<>(); Set 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 same values for all elements, See How HashSet works internally in Java for more details.
Here is the example to obtain a ConcurrentHashSet using keySet(mapped value) method in Java 8:
ConcurrentHashMap certificationCosts = new ConcurrentHashMap<>(); Set concurrentHashSet = certificationCosts.keySet(246); concurrentSet.add("Spring enterprise"); // value will be 246 but no error
you can also perform other Set operations e.g. 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 Java SE 8 for the Really Impatient.
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 allows 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 only suitable for application where set size is small and 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.
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 remove operation.
5. Traversal via iterators is fast and cannot encounter interference from other threads.
6. Iterators rely on unchanging snapshots of the array at the time the iterators were constructed.
That’s all about how to create ConcurrentHashSet in Java 8. The JDK 8 API not only has major features like lambda expression and stream but also these kinds of small changes which make your day to day coding easier. IT’s not super 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 set view returned by keySet() which doesn’t allow you to add new elements into the Set.
Further Reading
- From Collections to Streams in Java 8 Using Lambda Expressions
- Streams, Collectors, and Optionals for Data Processing in Java 8
- Java 8 in Action
Related articles:
How to write Comparator in Java 8?
how to read File in Java 8?
How to join String in Java 8?
How to compare Dates in Java 8?
How to format Date in Java 8?
How to sort a List in Java 8?
Thanks a lot for reading this article. If you like this tutorial then please share with your friends and colleagues.
Reference: | How to create a thread-safe ConcurrentHashSet in Java 8? from our JCG partner Javin Paul at the Javarevisited blog. |