(juc Series) synchronization list copyonwritearraylist and set source code analysis

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

  1. 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
  2. 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

Posted on Wed, 10 Nov 2021 03:39:10 -0500 by danc81