The CopyOnWriteArraySet is part of the java.util.concurrent
package and is a thread-safe variant of Set and is backed by a CopyOnWriteArrayList. It is quite helpful in scenarios where reads are more frequent than writes.
1. How does CopyOnWriteArraySet Work?
Similar to CopyOnWriteArrayList, it is an immutable snapshot-style iterator method. Any modification to the collection creates a new copy of the underlying array.
- When we call
add()
, it checks whether the element already exists in the underlying array:- If the element is not present, it creates a new copy of the array, adds the element to it, and replaces the old array with the new one.
- If the element already exists, the set remains unchanged.
- When we call
remove()
, it creates a new copy of the array, excluding the element to be removed. The old array is discarded, and the new array becomes the active data structure. - When we iterate, the iteration happens on a snapshot of the array that existed at the time the iterator was created.
- Iterators are immutable, and no changes to the set during iteration affect the iterator.
- No ConcurrentModificationException is thrown, even in concurrent environments.
Since each modification creates a new array under the hood, the CopyOnWriteArraySet is not suitable for large datasets or frequent writes.
2. Syntax
Creating a CopyOnWriteArraySet is straightforward. Here, E
represents the type of elements the set will hold.
import java.util.concurrent.CopyOnWriteArraySet;
CopyOnWriteArraySet<E> set = new CopyOnWriteArraySet<>()
3. Common Methods
The following methods are commonly used while interacting with the CopyOnWriteArraySet.
Method | Description |
---|---|
add(E e) | Adds the specified element if it is not already present. |
add(collection) | Adds all of the elements in the specified collection to the set. |
remove(Object o) | Removes the specified element, if present. |
contains(Object o) | Checks if the set contains the specified element. |
size() | Returns the number of elements in the set. |
iterator() | Returns an iterator over the elements. |
clear() | Removes all elements from the set. |
isEmpty() | Returns true if the set contains no elements. |
4. CopyOnWriteArraySet Example
Let us see a simple example of how to use the CopyOnWriteArraySet class.
CopyOnWriteArraySet<String> set = new CopyOnWriteArraySet<>();
// Adding elements
set.add("Apple");
set.add("Banana");
set.add("Cherry");
set.add("Apple"); // Duplicate element, won't be added
// Display the set
System.out.println("Set: " + set); // Output: [Apple, Banana, Cherry]
// Check for an element
System.out.println("Contains 'Banana': " + set.contains("Banana")); // Output: true
// Remove an element
set.remove("Banana");
System.out.println("Set after removal: " + set); // Output: [Apple, Cherry]
5. No ConcurrentModificationException is thrown
When an iterator is obtained from a CopyOnWriteArraySet
, it operates on a snapshot of the array at the time the iterator was created. Since each modification creates a new array, different threads operating concurrently on the CopyOnWriteArraySet are essentially working on different arrays.
As the underlying array might change due to concurrent modifications, the iterator’s view remains unchanged, preventing the ConcurrentModificationException. This mechanism guarantees thread safety without the need for explicit synchronization during iteration.
CopyOnWriteArraySet<Integer> set = new CopyOnWriteArraySet<>();
set.add(1);
set.add(2);
set.add(3);
// Start a thread to modify the set
new Thread(() -> {
set.add(4);
set.remove(1);
}).start();
// Iterate over the set
for (int num : set) {
System.out.println("Element: " + num);
// Even if the set is modified, no ConcurrentModificationException is thrown
}
6. When to Use?
Use CopyOnWriteArraySet in applications where set sizes generally stay small, read-only operations vastly outnumber mutative operations, and we need to prevent interference among threads during traversal.
For example, we can use CopyOnWriteArraySet to maintain a list of event listeners where additions/removals are infrequent, but notifying listeners happens often.
import java.util.concurrent.CopyOnWriteArraySet;
public class ListenerManager {
private final CopyOnWriteArraySet<String> listeners = new CopyOnWriteArraySet<>();
public void addListener(String listener) {
listeners.add(listener);
}
public void removeListener(String listener) {
listeners.remove(listener);
}
public void notifyListeners(String event) {
for (String listener : listeners) {
System.out.println("Notifying " + listener + " of event: " + event);
}
}
public static void main(String[] args) {
ListenerManager manager = new ListenerManager();
manager.addListener("Listener1");
manager.addListener("Listener2");
manager.notifyListeners("Event1");
}
}
CopyOnWriteArraySet helps minimize programmer-controlled synchronization steps and moves control to inbuilt, well-tested APIs.
Please note that for add operations, CopyOnWriteArraySet performs worse than HashSet due to the added step of creating a new backing array every time the set is updated. There is no performance overhead on read operations; both classes perform the same.
7. Comparison with Other Collections
Let’s compare the CopyOnWriteArraySet with other sets in the collection framework:
Feature | CopyOnWriteArraySet | HashSet | ConcurrentHashMap |
---|---|---|---|
Thread-Safe | Yes | No | Yes |
Allows Duplicates | No | No | No |
Performance on Reads | Fast | Fast | Fast |
Performance on Writes | Expensive (copy on write) | Fast | Moderate |
Consistent Snapshot During Iteration | Yes | No | No |
8. Conclusion
This Java Collection tutorial taught us to use the CopyOnWriteArraySet class, its constructors, methods, and usecases. We learned the internal working of CopyOnWriteArraySet and how it differs from CopyOnWriteArrayList.
Drop me your questions in the comments.
Happy Learning !!
Comments