Java Concurrent HashSet Equivalent to ConcurrentHashMap – Java并发HashSet等同于ConcurrentHashMap

最后修改: 2022年 1月 23日

1. Overview


In this tutorial, we’ll see what are the possibilities to create thread-safe HashSet instances and what is the equivalent of the ConcurrentHashMap for HashSet. Furthermore, we’ll look at the benefits and drawbacks of each approach.


2. Thread Safe HashSet Using ConcurrentHashMap Factory Method


Firstly we’ll look at the ConcurrentHashMap class that exposed the static newKeySet() method. Basically, this method returns an instance that respects the java.util.Set interface and allows the usage of standard methods like add(), contains(), etc.

首先我们来看看ConcurrentHashMap类,它暴露了静态newKeySet()方法。基本上,这个方法返回一个尊重java.util.Set接口的实例,并允许使用标准方法,如add(), contains(),等。

This can be created simply as:


Set<Integer> threadSafeUniqueNumbers = ConcurrentHashMap.newKeySet();

Furthermore, the performance of the returned Set is similar to the HashSet, as both are implemented using a hash-based algorithm. Moreover, the added overhead imposed by the synchronization logic is also minimal because the implementation uses a ConcurrentHashMap.


Lastly, the drawback is that the method exists only starting with Java 8.

最后,缺点是该方法仅从Java 8开始存在

3. Thread Safe HashSet Using ConcurrentHashMap Instance Methods


So far, we have looked at the static method of ConcurrentHashMap. Next, we’ll tackle the instance methods available for ConcurrentHashMap to create thread-safe Set instances. There are two methods available, newKeySet() and newKeySet(defaultValue) which slightly differ from each other.


Both methods create a Set, which is linked with the original map. To put it differently, each time we add a new entry to the originating ConcurrentHashMap, the Set will receive that value. Further, let’s look at the differences between these two methods.


3.1. The newKeySet() Method


As mentioned above, newKeySet() exposes a Set containing all keys of the originating map. The key difference between this method and the newKeySet(defaultValue) is that the current one doesn’t support adding new elements to the Set. So if we try to call methods like add() or addAll(), we’ll get an UnsupportedOperationException.


Although operations like remove(object) or clear() are working as expected, we need to be aware that any change on the Set will be reflected in the original map:


ConcurrentHashMap<Integer,String> numbersMap = new ConcurrentHashMap<>();
Set<Integer> numbersSet = numbersMap.keySet();

numbersMap.put(1, "One");
numbersMap.put(2, "Two");
numbersMap.put(3, "Three");

System.out.println("Map before remove: "+ numbersMap);
System.out.println("Set before remove: "+ numbersSet);


System.out.println("Set after remove: "+ numbersSet);
System.out.println("Map after remove: "+ numbersMap);

Next is the output of the code above:


Map before remove: {1=One, 2=Two, 3=Three}
Set before remove: [1, 2, 3]

Set after remove: [1, 3]
Map after remove: {1=One, 3=Three}

3.2. The newKeySet(defaultValue) Method


Let’s look at another way to create a Set out of the keys in the map. Compared with the one mentioned above, newKeySet(defaultValue) returns a Set instance that supports adding of new elements by calling add() or addAll() on the set.


Further looking at the default value passed as a parameter, this is used as the value for each new entry in the map added thought add() or addAll() methods. The following example shows how this works:


ConcurrentHashMap<Integer,String> numbersMap = new ConcurrentHashMap<>();
Set<Integer> numbersSet = numbersMap.keySet("SET-ENTRY");

numbersMap.put(1, "One");
numbersMap.put(2, "Two");
numbersMap.put(3, "Three");

System.out.println("Map before add: "+ numbersMap);
System.out.println("Set before add: "+ numbersSet);


System.out.println("Map after add: "+ numbersMap);
System.out.println("Set after add: "+ numbersSet);

Below is the output of the code above:


Map before add: {1=One, 2=Two, 3=Three}
Set before add: [1, 2, 3]
Map after add: {1=One, 2=Two, 3=Three, 4=SET-ENTRY, 5=SET-ENTRY}
Set after add: [1, 2, 3, 4, 5]

4. Thread Safe HashSet Using Collections Utility Class


Let’s use the synchronizedSet() method available in java.util.Collections to create a thread-safe HashSet instance:


Set<Integer> syncNumbers = Collections.synchronizedSet(new HashSet<>());

Before using this approach, we need to be aware that it’s less efficient than the ones discussed above. Basically, synchronizedSet() just wraps the Set instance into a synchronized decorator compared with ConcurrentHashMap that implements a low-level concurrency mechanism.


5. Thread Safe Set Using CopyOnWriteArraySet


The last approach to create thread-safe Set implementations is the CopyOnWriteArraySet. Creating an instance of this Set is simple:


Set<Integer> copyOnArraySet = new CopyOnWriteArraySet<>();

Although it looks appealing to use this class, we need to consider some serious performance drawbacks. Behind the scene, CopyOnWriteArraySet uses an Array, not a HashMap, to store the data. This means that operations like contains() or remove() have O(n) complexity, while when using Sets backed by ConcurrentHashMap, the complexity is O(1).


It is recommended is to use this implementation when the Set size stays generally small and read-only operations have a majority.


6. Conclusions


In this article, we have seen different possibilities to create thread-safe Set instances and emphasized the differences between them. Firstly we have seen the ConcurrentHashMap.newKeySet() static method. This should be the first choice when a thread-safe HashSet is needed. Afterwards we looked what are the differences between ConcurrentHashMap static method and newKeySet(), newKeySet(defaultValue)  for ConcurrentHashMap instances.

在这篇文章中,我们看到了创建线程安全的Set实例的不同可能性,并强调了它们之间的差异。首先我们看到了ConcurrentHashMap.newKeySet() 静态方法。当需要一个线程安全的HashSet时,这应该是第一选择。之后我们看了一下ConcurrentHashMap静态方法和newKeySet(), newKeySet(defaultValue)之间的区别,对于ConcurrentHashMap实例。

Finally we discussed also Collections.synchronizedSet() and the CopyOnWriteArraySet and there performance drawbacks.


As usual, the complete source code is available over on GitHub.