The source code of this article is based on JDK13
CopyOnWriteArrayList
Official annotation translation
ArrayList is a thread - safe variant. All variable operations (such as add/set) use a copy of the underlying array
This is often very expensive, but it is more efficient when convenient operations are far greater than this operation. This class is useful if you don't want or can't do synchronous traversal
The snapshot style traverser uses a reference to the array state when the traverser is created. This reference will not change in the traversal life cycle. Therefore, there is no interference and the iterator will not throw ConcurrentModificationException
After the iterator is created, all add, remove and other change operations will not be reflected. The iterator does not support the change operation of elements, and these methods throw exceptions
All elements are supported, including null
Source code
definition
public class CopyOnWriteArrayList<E> implements List<E>, RandomAccess, Cloneable, java.io.Serializable { private static final long serialVersionUID = 8673264195747942595L;
A list
attribute
final transient Object lock = new Object(); private transient volatile Object[] array;
A lock is used to lock the array, an array that actually holds data
Why use a built-in lock instead of ReentrantLock?
Note: if both are OK, we prefer (moderate preference) built-in locks.
Construction method
public CopyOnWriteArrayList() { setArray(new Object[0]); } public CopyOnWriteArrayList(Collection<? extends E> c) { Object[] es; if (c.getClass() == CopyOnWriteArrayList.class) es = ((CopyOnWriteArrayList<?>)c).getArray(); else { es = c.toArray(); // defend against c.toArray (incorrectly) not returning Object[] // (see e.g. https://bugs.openjdk.java.net/browse/JDK-6260652) if (es.getClass() != Object[].class) es = Arrays.copyOf(es, es.length, Object[].class); } setArray(es); } public CopyOnWriteArrayList(E[] toCopyIn) { setArray(Arrays.copyOf(toCopyIn, toCopyIn.length, Object[].class)); }
Three construction methods are created respectively
- Empty list
- Create a list from an existing collection
- Create a list from an existing array
add method
As a list, the important interfaces are add, delete, obtain and traverse. First, let's take a look at add
public boolean add(E e) { // Lock synchronized (lock) { // Get array Object[] es = getArray(); int len = es.length; // Array copy es = Arrays.copyOf(es, len + 1); // The new element is placed last es[len] = e; // Put the array back setArray(es); return true; } }
This add logic is not difficult. Just look at the comments. The key point is that the performance of this full array copy looks very general
Add to specified location
public void add(int index, E element) { // Lock synchronized (lock) { Object[] es = getArray(); int len = es.length; // Parameter check if (index > len || index < 0) throw new IndexOutOfBoundsException(outOfBounds(index, len)); Object[] newElements; // Elements to be moved int numMoved = len - index; if (numMoved == 0) // No need to move, direct copy newElements = Arrays.copyOf(es, len + 1); else { // Copy part newElements = new Object[len + 1]; System.arraycopy(es, 0, newElements, 0, index); System.arraycopy(es, index, newElements, index + 1, numMoved); } // Place the element at the specified location newElements[index] = element; // Put back array setArray(newElements); } }
There's nothing to say. Just lock it directly, then calculate the elements to be moved, and copy the array
get method
public E get(int index) { return elementAt(getArray(), index); } static <E> E elementAt(Object[] a, int index) { return (E) a[index]; }
Because the array is used to save data, the index operation is relatively simple. You can directly obtain the subscript corresponding to the array
remove method
- Remove by subscript
public E remove(int index) { // Lock synchronized (lock) { Object[] es = getArray(); int len = es.length; // The element was found E oldValue = elementAt(es, index); // Calculate the data to be moved int numMoved = len - index - 1; Object[] newElements; // Copy if (numMoved == 0) newElements = Arrays.copyOf(es, len - 1); else { newElements = new Object[len - 1]; System.arraycopy(es, 0, newElements, 0, index); System.arraycopy(es, index + 1, newElements, index, numMoved); } // Set new array setArray(newElements); return oldValue; } }
Lock directly, and then copy the element according to the specified subscript to empty the position
- Remove by element
public boolean remove(Object o) { Object[] snapshot = getArray(); int index = indexOfRange(o, snapshot, 0, snapshot.length); return index >= 0 && remove(o, snapshot, index); }
First, find the corresponding subscript based on the element, then call the element to remove the element from the subscript.
iterator
public Iterator<E> iterator() { return new COWIterator<E>(getArray(), 0); }
At the moment of calling to get the iterator, get a copy of the internal array and construct a new iterator
Take a look at the implementation of this iterator
static final class COWIterator<E> implements ListIterator<E> { // Snapshot of data and traversal pointer private final Object[] snapshot; private int cursor; COWIterator(Object[] es, int initialCursor) { cursor = initialCursor; snapshot = es; } // Is there a next one public boolean hasNext() { return cursor < snapshot.length; } // Is there a previous one public boolean hasPrevious() { return cursor > 0; } // Get the next, just move the pointer @SuppressWarnings("unchecked") public E next() { if (! hasNext()) throw new NoSuchElementException(); return (E) snapshot[cursor++]; } @SuppressWarnings("unchecked") public E previous() { if (! hasPrevious()) throw new NoSuchElementException(); return (E) snapshot[--cursor]; } public int nextIndex() { return cursor; } public int previousIndex() { return cursor - 1; } /** * Not supported. Always throws UnsupportedOperationException. * @throws UnsupportedOperationException always; {@code remove} * is not supported by this iterator. */ public void remove() { throw new UnsupportedOperationException(); } /** * Not supported. Always throws UnsupportedOperationException. * @throws UnsupportedOperationException always; {@code set} * is not supported by this iterator. */ public void set(E e) { throw new UnsupportedOperationException(); } /** * Not supported. Always throws UnsupportedOperationException. * @throws UnsupportedOperationException always; {@code add} * is not supported by this iterator. */ public void add(E e) { throw new UnsupportedOperationException(); } @Override public void forEachRemaining(Consumer<? super E> action) { Objects.requireNonNull(action); final int size = snapshot.length; int i = cursor; cursor = size; for (; i < size; i++) action.accept(elementAt(snapshot, i)); } }
Save a snapshot of the array and the moving pointer. It should be noted that all change operations are not supported
For each call to next, the pointer can be moved backward
summary
CopyOnWriteArrayList uses an array to save data, and internally uses synchronized for synchronization
- Every time you change an element, you need to lock it and then copy the array in full. Therefore, it can be predicted that the efficiency of adding and removing elements is average
- During traversal, take a snapshot of the current array at the moment when the iterator is called, and then access the snapshot. All changes between are invisible, and the iterator does not support element changes
It is suitable for concurrent scenarios with more reads and less writes
CopyOnWriteArraySet
Official annotation translation
A Set that uses CopyOnWriteArrayList for all operations. Therefore, it shares the following properties with CopyOnWriteArrayList:
- The most trial and write less application scenarios, and you need to reduce the interference between threads during traversal
- Thread safety
- Change operations are time consuming because they usually copy the entire underlying array inside
- The traverser does not support all change operations
- The traversal operation using the iterator is fast, but can not show the interference of other threads. The iterator depends on the unchangeable array snapshot
Simple usage examples:
class Handler { void handle(); ... } class X { private final CopyOnWriteArraySet<Handler> handlers = new CopyOnWriteArraySet<>(); public void addHandler(Handler h) { handlers.add(h); } private long internalState; private synchronized void changeState() { internalState = ...; } public void update() { changeState(); for (Handler handler : handlers) handler.handle(); } }
CopyOnWriteArraySet is used to save a series of processors. When the state changes, it needs to respond to the change
Source code
definition
public class CopyOnWriteArraySet<E> extends AbstractSet<E> implements java.io.Serializable {
A nonsense: it implements the basic Set interface
attribute
private final CopyOnWriteArrayList<E> al;
As mentioned earlier, CopyOnWriteArrayLis is used internally, so the attribute is very simple. Only CopyOnWriteArrayLis that actually stores data is saved
Construction method
public CopyOnWriteArraySet() { al = new CopyOnWriteArrayList<E>(); } public CopyOnWriteArraySet(Collection<? extends E> c) { if (c.getClass() == CopyOnWriteArraySet.class) { @SuppressWarnings("unchecked") CopyOnWriteArraySet<E> cc = (CopyOnWriteArraySet<E>)c; al = new CopyOnWriteArrayList<E>(cc.al); } else { al = new CopyOnWriteArrayList<E>(); al.addAllAbsent(c); } }
There are two construction methods, one is to create an empty set, and the other is to add all elements to the new set according to the given set
add method
public boolean add(E e) { return al.addIfAbsent(e); }
Call the addIfAbsent method of CopyOnWriteArrayLis. If the element does not exist, add
The uniqueness semantics of Set is realized by using the underlying addIfAbsent
Remove Method
public boolean remove(Object o) { return al.remove(o); }
Simply call the remove method of CopyOnWriteArrayLis
Since Set has no index / subscript and other concepts by definition, the main method is to add, remove and traverse
ergodic
public Iterator<E> iterator() { return al.iterator(); }
Called the traverser of the underlying implementation
summary
Too much water, i....
CopyOnWriteArrayList is used to implement the semantics of a Set. There is only one change based on CopyOnWriteArrayList
When adding, you need to judge whether the element already exists in the collection, which is implemented by AddIfAbsent
All other properties are completely consistent with CopyOnWriteArrayList
Reference articles
End.
Contact me
Finally, welcome to my personal official account, Yan Yan ten, which will update many learning notes from the backend engineers. I also welcome direct official account or personal mail or email to contact me.
The above are all personal thoughts. If there are any mistakes, please correct them in the comment area.
Welcome to reprint, please sign and keep the original link.
Contact email: huyanshi2580@gmail.com
For more study notes, see personal blog or WeChat official account, Yan Yan ten > > Huyan ten