October 10, 2022

Java CopyOnWriteArraySet With Examples

CopyOnWriteArraySet in Java extends the AbstractSet which in turn implements the Set interface and is part of java.util.concurrent package. How CopyOnWriteArraySet differs from other Set implementations in Java Collections framework is that it is thread-safe.

CopyOnWriteArraySet internal implementation in Java

CopyOnWriteArraySet internally uses CopyOnWriteArrayList for all of its operations and shares the same basic properties. With in the CopyOnWriteArraySet class in Java a CopyOnWriteArrayList is defined as follows-

private final CopyOnWriteArrayList<E> al;

When a CopyOnwriteArraySet is created this CopyOnWriteArrayList field is initialized and used for storing elements.

For example when a CopyOnwriteArraySet is created using a constructor with no-args.

public CopyOnWriteArraySet() {
  al = new CopyOnWriteArrayList<E>();
}

Features of the CopyOnWriteArraySet

Some of the features of the CopyOnWriteArraySet in Java discussed in this post are-

  1. CopyOnWriteArraySet is a Set implementation so duplicate elements are not allowed.
  2. CopyOnWriteArraySet is thread-safe.
  3. Since CopyOnWriteArraySet internally uses CopyOnWriteArrayList so just like in CopyOnWriteArrayList all mutative operations (add, set, and so on) create a separate copy of the underlying array so that there is no thread interference.
  4. Iterator returned by CopyOnWriteArraySet in Java is fail-safe which means iterator is guaranteed not to throw ConcurrentModificationException even if the Set is structurally modified at any time after the iterator is created.
  5. Iterator’s element changing operations like add, remove are not supported and throw UnsupportedOperationException.

Java CopyOnWriteArraySet constructors

  • CopyOnWriteArraySet()- Creates an empty set.
  • CopyOnWriteArraySet(Collection<? extends E> c)- Creates a set containing all of the elements of the specified collection.

Java example creating a CopyOnWriteArraySet

Here is a simple example showing how to create CopyOnWriteArraySet and add elements to it.

public class ConcurrentSet {
  public static void main(String[] args) {
    Set<String> carSet = new CopyOnWriteArraySet<String>();
    carSet.add("Audi");
    carSet.add("Jaguar");
    carSet.add("BMW");
    carSet.add("Mini Cooper");
    carSet.add("BMW");
    carSet.add(null);
    for(String car : carSet) {
      System.out.println("Car- " + car);
    }
  }
}
Output
Car- Audi
Car- Jaguar
Car- BMW
Car- Mini Cooper
Car- null

As you can see from the output duplicates are not allowed in CopyOnWriteArraySet even if “BMW” was added twice it’s stored only once. Also one null value is allowed.

CopyOnWriteArraySet returns a fail-safe iterator

Iterator returned by CopyOnWriteArraySet in Java is fail-safe which means iterator is guaranteed not to throw ConcurrentModificationException even if the Set is structurally modified at any time after the iterator is created.

When an iterator is created for the CopyOnWriteArraySet it gets an immutable copy of the underlying array which is iterated. This array never changes during the lifetime of the iterator, so interference is impossible.

But note that since iteration is done on a separate copy so any modification in the CopyOnWriteArraySet won’t be reflected while iteration.

CopyOnWriteArraySet iteration example

Let’s see an example of iteration in CopyOnWriteArraySet. To make it clearer first we’ll iterate a HashSet while it is also modified concurrently by another thread to see what happens in the case of HashSet which has a fail-fast iterator then we’ll see the same example using CopyOnWriteArraySet.

public class SetItr {
  public static void main(String[] args) {
    Set<String> carSet = new HashSet<String>();
    carSet.add("Audi");
    carSet.add("Jaguar");
    carSet.add("BMW");
    carSet.add("Mini Cooper");
    Thread t1 = new Thread(new ItrSet(carSet));
    Thread t2 = new Thread(new ModSet(carSet));
    t1.start();
    t2.start();
    try {
      t1.join();
      t2.join();
    } catch (InterruptedException e) {
      // TODO Auto-generated catch block
      e.printStackTrace();
    }
  }
}

//Thread class for iteration
class ItrSet implements Runnable{
  Set<String> carSet; 
  public ItrSet(Set<String> carSet){
    this.carSet = carSet;
  }
  @Override
  public void run() {
    Iterator<String> i = carSet.iterator(); 
    while (i.hasNext()){ 
      System.out.println(i.next()); 
      try {
        Thread.sleep(500);
      } catch (InterruptedException e) {
        // TODO Auto-generated catch block
        e.printStackTrace();
      } 
    }     
  }
}

//Thread class for modifying Set
class ModSet implements Runnable{
  Set<String> carSet; 
  public ModSet(Set<String> carSet){
    this.carSet = carSet;
  }
  @Override
  public void run() {
    System.out.println("Adding new value to the Set"); 
    carSet.add("Mercedes");  
  }     
}
Output
Adding new value to the Set
Audi
Exception in thread "Thread-0" java.util.ConcurrentModificationException
	at java.base/java.util.HashMap$HashIterator.nextNode(HashMap.java:1498)
	at java.base/java.util.HashMap$KeyIterator.next(HashMap.java:1521)
	at com.knpcode.ItrSet.run(SetItr.java:40)
	at java.base/java.lang.Thread.run(Thread.java:844)

As you can see ConcurrentModificationException is thrown as structural modification is detected while iterating the HashSet.

Using CopyOnWriteArraySet

By changing the HashSet to the CopyOnWriteArraySet in the same code and running it.

Set<String> carSet = new CopyOnWriteArraySet<String>();

You can get the output as-

Adding new value to the Set
Audi
Jaguar
BMW
Mini Cooper

Now ConcurrentModificationException is not thrown but the new value which is added is not displayed in the iteration as the iteration is done on a separate copy.

Iterator’s add, remove method not permitted in CopyOnWriteArraySet

Since iteration of the elements in CopyOnWriteArraySet is done on a separate copy iterator’s element-changing operations like remove are not supported. These methods throw UnsupportedOperationException.

public class SetItr {
  public static void main(String[] args) {
    Set<String> carSet = new CopyOnWriteArraySet<String>();
    carSet.add("Audi");
    carSet.add("Jaguar");
    carSet.add("BMW");
    carSet.add("Mini Cooper");
    Iterator<String> itr = carSet.iterator(); 
    while (itr.hasNext()){ 
      String str = itr.next();
      if(str.equals("BMW")) {
        // removing using iterator's remove method
        itr.remove();
      }
    }
  }
}
Output
Exception in thread "main" java.lang.UnsupportedOperationException
at java.base/java.util.concurrent.CopyOnWriteArrayList$COWIterator.remove(CopyOnWriteArrayList.java:1117)
at com.knpcode.SetItr.main(SetItr.java:21)

Important points about CopyOnWriteArraySet

  1. CopyOnWriteArraySet is best suited for applications in which set sizes are small, there are more read-only operations than mutative operations and you need to prevent interference among threads during traversal.
  2. Mutative operations (add, set, remove, etc.) are costly because of the added task of creating copies of the underlying array.
  3. CopyOnWriteArraySet is guaranteed to not throw ConcurrentModificationException even if there are concurrent modifications to the Set while iteration. At the same time iterator’s element-changing operations like remove are not supported.

That's all for the topic Java CopyOnWriteArraySet With Examples. If something is missing or you have something to share about the topic please write a comment.


You may also like

No comments:

Post a Comment