Last Updated on 13 August 2019   |   Print Email
This Java Concurrency tutorial helps you understand how to use the CopyOnWriteArray collection in the java.util.concurrent package.
1. Why CopyOnWriteArrayList?
Basically, a CopyOnWriteArrayList is similar to an ArrayList, with some additional and more advanced thread-safe features.You know, ArrayList is not thread-safe so it’s not safe to use in multi-threaded applications. We can achieve thread-safe feature for an ArrayList by using a synchronized wrapper like this:
List<String> unsafeList = new ArrayList<>();
List<String> safeList = Collections.synchronizedList(unsafeList);
safeList.add("Boom"); // safe to use with multiple threads
However, this synchronized list has a limitation: all of its read and write methods (add, set, remove, iterator, etc) are synchronized on the list object itself. That means if a thread is executing add() method, it blocks other threads which want to get the iterator to access elements in the list, for example. Also, only one thread can iterate the list’s elements at a time, which can be inefficient. That’s quite rigid.What if we want a more flexible collection which allows:
One thread executing read operation and another executing write operation concurrently.
Only one thread can execute write operation while other threads can execute read operations simultaneously.
The CopyOnWriteArrayList class is designed to enable such sequential write and concurrent reads features. For example, we can write a multi-threaded program that allows one thread to add elements to the list while other threads are traversing the list’s elements at the same time, and no worry about ConcurrentModificationException as per the case of a synchronized list.That’s interesting. So how does the CopyOnWriteArrayList implement this concurrent feature?
2. How does CopyOnWriteArrayList works?
The CopyOnWriteArrayList class uses a mechanism called copy-on-write which works like this: For every write operation (add, set, remove, etc) it makes a new copy of the elements in the list. That means the read operations (get, iterator, listIterator, etc) work on a different copy.In addition, a thread must acquire a separate lock before executing a write operation, and all write operations use this same lock so there’s only one write operation can be executed by only one thread at a time. The read operations do not use any lock so multiple threads can execute multiple read operations simultaneously. And of course, read and write operations do not block each other.The methods iterator() and listIterator() return an iterator object that holds different copy of the elements, hence the term snapshot iterator. The snapshot iterator doesn’t allow modifying the list while traversing, and it will not throw ConcurrentModificationException if the list is being modified by other thread during the traversal, and the read and write operations work on different copies of elements.
3. When to Use CopyOnWriteArrayList?
Due to its special behaviors, CopyOnWriteArrayList is suitable for use in scenarios require sequential write and concurrent reads on a same collection. But you should take performance issue into consideration because the process of copying elements is costly for a list that has a large number of elements and many write operations.Having said that, use CopyOnWriteArrayList only when the number of write operations is very small as compared to the read operations and the list contains a small number of elements.In some cases, we can use CopyOnWriteArrayList as a thread-safe alternative to ArrayList, and to take advantages of its new methods addIfAbsent() and addAllAbsent(), which are explained below.
4. Understanding CopyOnWriteArrayList API
CopyOnWriteArrayList is a member of the Java Collection framework and is an implementation the List interface so it has all typical behaviors of a list. CopyOnWriteArrayList is considered as a thread-safe alternative to ArrayList with some differences:
You can pass an array when creating a new CopyOnWriteArrayList object. The list holds a copy of this array, for example:
String[] fruits = {"Apple", "Banana", "Lemon", "Grape", "Mango"};
List<String> list = new CopyOnWriteArrayList<>(fruits);
Though a list allows duplicate elements, you can add an element to the list if and only if it is not already in the list, by using the method addIfAbsent(element). More importantly, this method is thread-safe which means it guarantees no other threads can add the same element at the same time. This method returns true if the element was added. For example:
CopyOnWriteArrayList<String> list = new CopyOnWriteArrayList<>();
list.add("Apple");
list.add("Banana");
if (list.addIfAbsent("Orange")) {
System.out.println("Orange was added");
}
Similarly, the method addAllAbsent(Collection) appends all elements in the specified collection that are not already contained in the list. And more importantly, this method is thread-safe. This method returns the number of elements were added. For example:
CopyOnWriteArrayList<String> list1 = new CopyOnWriteArrayList<>();
list1.add("Apple");
list1.add("Banana");
List<String> list2 = Arrays.asList("Lemon", "Banana");
int result = list1.addAllAbsent(list2);
System.out.println("Elements added: " + result);
This print Elements added: 1 because the element Banana is already contained in the list1.
The method iterator() returns a generic Iterator that holds a snapshot of the list. This iterator doesn’t support the remove() method.
The method listIerator() returns a generic ListIterator that holds a snapshot of the list. This iterator doesn’t support the remove(), set() or add() method.
And as stated previously, the iterator will not throw ConcurrentModificationException if the list is being modified by another thread while the current thread is traversing the iterator, because a snapshot iterator holds a different copy of elements.
5. Java CopyOnWriteArrayList Examples
Let’s see a couple of examples in action. The first one creates two threads:- Thread Writer adds a number to CopyOnWriteArrayList for every 5 seconds.- Thread Reader iterates the list repeatedly with a small delay (10 milliseconds) for every iteration.That means the read operations outnumber the write ones, and here’s the full source code of the program:
import java.util.*;
import java.util.concurrent.*;
/**
*
* This program demonstrates how CopyOnWriteArrayList works.
*
* @author www.codejava.net
*/
public class CopyOnWriteArrayListExample {
public static void main(String[] args) {
List<Integer> list = new CopyOnWriteArrayList<>();
list.add(1);
list.add(2);
list.add(3);
list.add(4);
list.add(5);
new WriteThread("Writer", list).start();
new ReadThread("Reader", list).start();
}
}
class WriteThread extends Thread {
private List<Integer> list;
public WriteThread(String name, List<Integer> list) {
this.list = list;
super.setName(name);
}
public void run() {
int count = 6;
while (true) {
try {
Thread.sleep(5000);
} catch (InterruptedException ex) {
ex.printStackTrace();
}
list.add(count++);
System.out.println(super.getName() + " done");
}
}
}
class ReadThread extends Thread {
private List<Integer> list;
public ReadThread(String name, List<Integer> list) {
this.list = list;
super.setName(name);
}
public void run() {
while (true) {
String output = "\n" + super.getName() + ":";
Iterator<Integer> iterator = list.iterator();
while (iterator.hasNext()) {
Integer next = iterator.next();
output += " " + next;
// fake processing time
try {
Thread.sleep(10);
} catch (InterruptedException ex) {
ex.printStackTrace();
}
}
System.out.println(output);
}
}
}
Run this program and observe the result. You will see that the reader thread constantly prints out the elements in the list, whereas the writer thread slowly adds a new number to the list. This program runs forever until you press Ctrl + C to stop it.Try to change the list implementation from CopyOnWriteArrayList to ArrayList like this:
List<Integer> list = new ArrayList<>();
Recompile and run the program again, you will see that the reader thread throws ConcurrentModificationExceptionas soon as the writer thread adds a new element to the list. The reader thread die and only the writer thread alive.The second example demonstrates how to use CopyOnWriteArrayList in event handling. Consider the following class:
/**
*
* This program demonstrates how CopyOnWriteArrayList is used.
*
* @author www.codejava.net
*/
class GuiComponent {
private List<ActionListener> listeners = new CopyOnWriteArrayList<>();
public void addActionListener(ActionListener listener) {
listeners.add(listener);
}
public void removeActionListener(ActionListener listener) {
listeners.remove(listener);
}
public void fireActionEvent() {
for (ActionListener listener : listeners) {
listener.actionPerformed(new ActionEvent(this, "message"));
}
}
}
Suppose this class represents a GUI component which can receives events such as mouse click. The client code can register (subscribe) to receive notification when the event occurs via the following method:
public void addActionListener(ActionListener listener)
The components use a CopyOnWriteArrayList object to maintain all registered listeners:
private List<ActionListener> listeners = new CopyOnWriteArrayList<>();
When an event occurs, the component notifies all of its listeners by iterating the list and invoke the action handler method on each listener, as shown in the fireActionEvent() method:
for (ActionListener listener : listeners) {
listener.actionPerformed(new ActionEvent(this, "message"));
}
The ActionListener interface is defined as follows:
public interface ActionListener {
public void actionPerformed(ActionEvent evt);
}
And the ActionEvent class is implemented as follows:
public class ActionEvent {
Object source;
Object data;
public ActionEvent(Object source, Object data) {
this.source = source;
this.data = data;
}
}
Look at the GuiComponent class, a CopyOnWriteArrayList is used because:
It allows two threads can both read and write the list concurrently: one thread adds or removes a listener and the other thread notifies all listeners.
The number of listeners is changed infrequently, whereas the number of times the listeners are notified more frequently (the read operations outnumber the write ones).
It doesn’t throw ConcurrentModificationException if a thread is adding/removing a listener while another thread is iterating the list of listeners.
6. Summary
CopyOnWriteArrayList can be used as a thread-safe alternative to ArrayList, with additional methods addIfAbsent() and addAllAbsent() that append elements if they are not contained in the list. A CopyOnWriteArrayList makes a new copy of its elements for every write operation and its iterator holds a different copy (snapshot) so it enables sequential writes and concurrent reads: only one thread can execute write operation and multiple threads can execute read operations at the same time. And its iterator doesn’t throw ConcurrentModification.
Nam Ha Minh is certified Java programmer (SCJP and SCWCD). He began programming with Java back in the days of Java 1.4 and has been passionate about it ever since. You can connect with him on Facebook and watch his Java videos on YouTube.
Comments