Guava Sets.newConcurrentHashSet() method in Java
This post will discuss how to create a concurrent set in Java using Guava Sets.newConcurrentHashSet() method.
The Set collection is used to store and manipulate distinct elements. However, sometimes you might want to create a concurrent set that can be safely accessed and modified by multiple threads without explicit synchronization. For example, you might want to use a concurrent set to store the online users of your web application, or the active tasks of your background service. In this post, we will show you how to use the newConcurrentHashSet() method of the Sets class in Guava to create a concurrent set in Java.
1. Overview of newConcurrentHashSet() method
The newConcurrentHashSet() method of the Sets class returns a new instance of a Set that uses a ConcurrentHashMap for its internal implementation. A ConcurrentHashMap is a type of map that supports full concurrency of retrievals and high expected concurrency for updates. This means that multiple threads can read and write to the map without blocking each other, as long as they operate on different keys. The syntax of the Sets.newConcurrentHashSet() method is:
|
1 2 |
public static <E> Set<E> newConcurrentHashSet() public static <E> Set<E> newConcurrentHashSet(Iterable<? extends E> elements) |
The type parameter E is the type of elements in the set. The method returns a new concurrent hash set instance containing the given elements, or an empty set if no elements are specified.
The newConcurrentHashSet() method takes an optional parameter of an Iterable or an array that contains the initial elements of the set. Here is an example of using the Sets.newConcurrentHashSet() method:
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 |
import com.google.common.collect.Sets; import java.util.Arrays; import java.util.Set; class Main { public static void main(String[] args) { // create an empty concurrent hash set Set<Integer> emptySet = Sets.newConcurrentHashSet(); System.out.println(emptySet); // [] // create a concurrent hash set from an iterable Iterable<Integer> iterable = Arrays.asList(1, 2, 3, 4, 5); Set<Integer> setFromIterable = Sets.newConcurrentHashSet(iterable); System.out.println(setFromIterable); // [1, 2, 3, 4, 5] } } |
2. Importance of newConcurrentHashSet() method
The newConcurrentHashSet() method is useful when you want to create a concurrent set that can handle concurrent operations efficiently and safely. For example, you might want to:
- Add or remove elements from the set in different threads without worrying about synchronization or concurrency issues.
- Iterate over the elements of the set in one thread while modifying them in another thread without throwing
ConcurrentModificationException. - Perform bulk operations on the set such as
containsAll(),addAll(),removeAll(), orretainAll()with high performance and scalability.
All these scenarios can be handled using the newConcurrentHashSet() method, as it returns a set that supports all the methods of the Set interface and inherits the concurrency features of the ConcurrentHashMap. For example:
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 |
import com.google.common.collect.Sets; import java.util.Arrays; import java.util.Set; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; class Main { public static void main(String[] args) { // Create a concurrent set Set<String> users = Sets.newConcurrentHashSet(Arrays.asList("Mia")); // Create a concurrent set with some initial elements Set<String> tasks = Sets.newConcurrentHashSet(Arrays.asList("task1", "task2", "task3")); // Add or remove elements from the set in different threads ExecutorService executor = Executors.newFixedThreadPool(2); executor.submit(() -> users.add("David")); executor.submit(() -> users.remove("Mia")); // Iterate over the elements of the set in one thread while modifying them in another executor.submit(() -> { for (String user : users) { System.out.println(user); // David } }); executor.submit(() -> users.add("Michael")); // Perform bulk operations on the set Set<String> doneTasks = Sets.newConcurrentHashSet(Arrays.asList("task1", "task3")); tasks.retainAll(doneTasks); // keep only the done tasks System.out.println(tasks); // [task1, task3] } } |
3. Internal working of newConcurrentHashSet() method
You might wonder how the newConcurrentHashSet() method works internally, and whether it affects the performance or memory usage of your program. The answer is that it depends on the size and usage of the set.
The newConcurrentHashSet() method creates a wrapper around a ConcurrentHashMap instance that uses dummy values for its entries. This means that each element of the set is stored as a key in the map, and each value in the map is a constant object that has no meaning. For example, if you create a concurrent set with three elements "a", "b", and "c", the underlying map would look like this:
|
1 2 3 4 5 6 7 8 9 10 |
// The dummy value used for all entries private static final Object DUMMY_VALUE = new Object(); // The underlying map private final ConcurrentHashMap<E, Object> map; // The map after adding three elements map.put("a", DUMMY_VALUE); map.put("b", DUMMY_VALUE); map.put("c", DUMMY_VALUE); |
This design allows the concurrent set to delegate all its operations to the underlying map, which handles all the concurrency aspects. For example, when you call users.add("David"), it actually calls map.put("David", DUMMY_VALUE), which returns true if there was no previous entry for "David" in the map, or false otherwise.
The advantage of this design is that it leverages the existing functionality and performance of the ConcurrentHashMap class, which is well-tested and optimized for concurrent access and update. The disadvantage of this design is that it adds some memory overhead for storing the dummy values, which may be significant if you have a large number of elements in your set.
4. Benefits and drawbacks of using the newConcurrentHashSet() method
Using the newConcurrentHashSet() method of the Sets class in Guava has some benefits and drawbacks that you should be aware of before using it. Here are some of the benefits:
- It is easy to use, as you only need to call one method to create a concurrent set from an Iterable or an array.
- It is flexible and powerful, as you can use any type of elements that are compatible with the
ConcurrentHashMapclass, such as objects that implement theequals()andhashCode()methods correctly. - It is consistent and thread-safe, as it preserves the immutability and unmodifiability of the elements in the set.
- It may be confusing or surprising, as it returns a set that is backed by a map, meaning that some methods may behave differently than expected.
- It may be less performant than a regular set in some cases, such as when using a large number of elements or performing frequent iterations. This is because the
newConcurrentHashSet()method may add some memory overhead for storing the dummy values, and some time overhead for delegating the operations to the underlying map.
5. Conclusion
In this post, we have covered how to use the newConcurrentHashSet() method of the Sets class in Guava to create a concurrent set in Java. We have also explained why this method is useful and what are some of the benefits and drawbacks of using it.
If you want to learn more about this method, you can check out the Guava documentation and its GitHub repository.
Thanks for reading.
To share your code in the comments, please use our online compiler that supports C, C++, Java, Python, JavaScript, C#, PHP, and many more popular programming languages.
Like us? Refer us to your friends and support our growth. Happy coding :)