CopyOnWriteArrayList analysis

1. Properties start


    private static final long serialVersionUID = 8673264195747942595L;

    / / exclusive lock
    final transient ReentrantLock lock = new ReentrantLock();

 // Array methods are getArray and setArray
    private transient volatile Object[] array;
Copy the code

2. Start the construction method

  1. No arguments structure
// Assign an array of Object[0] with no length
public CopyOnWriteArrayList(a) {
        setArray(new Object[0]);
  }
Copy the code
  1. Passing a Collection

    Arraycopy: c becomes an array, copies a new array through array. copyOf, and assigns it to elements.

   public CopyOnWriteArrayList(Collection<? extends E> c) {
        Object[] elements;
        if(c.getClass() == CopyOnWriteArrayList.class) elements = ((CopyOnWriteArrayList<? >)c).getArray();else {
            elements = c.toArray();
            // c.toArray might (incorrectly) not return Object[] (see 6260652)
            if(elements.getClass() ! = Object[].class) elements = Arrays.copyOf(elements, elements.length, Object[].class); } setArray(elements); }Copy the code

The rest of the API is basically all about manipulating array elements, as we’ve seen in ArrayList. Add, set and GET methods are analyzed.

Add method analysis

The logic here is very simple. Each time we acquire the lock, we copy the array, each time we copy a new array of length + 1, and put the new data in the last position

    public boolean add(E e) {
        final ReentrantLock lock = this.lock;
        lock.lock();
        try {
            Object[] elements = getArray();
            int len = elements.length;
            Object[] newElements = Arrays.copyOf(elements, len + 1);
            newElements[len] = e;
            setArray(newElements);
            return true;
        } finally{ lock.unlock(); }}Copy the code

Set (int index, E element) analysis

Replaces the element with the specified subscript. If there are no elements at the specified location, the array is reset back.

If the specified location has a value, note that reference comparisons are used again. If not, it makes a new copy, assigns the location subscript, gets the new array, and then resets the value.

 public E set(int index, E element) {
        final ReentrantLock lock = this.lock;
        lock.lock();
        try {
            Object[] elements = getArray();
            E oldValue = get(elements, index);
            // Use the reference relation comparison here
            if(oldValue ! = element) {int len = elements.length;
                // Copy the new array
                Object[] newElements = Arrays.copyOf(elements, len);
               / / set the value
                newElements[index] = element;
               // Resets the new array to force the values of volatile variables to flush the working memory and fetch values from the working memory if they are fetched by other threads. Wrapped memory consistency
                setArray(newElements);
            } else {
                // Not quite a no-op; ensures volatile write semantics
                setArray(elements);
            }
            return oldValue;
        } finally{ lock.unlock(); }}Copy the code

Get method analysis

The get method is very simple, just get the corresponding element in the array by subscript. It’s really simple.

  public E get(int index) {
        return get(getArray(), index);
    }
  private E get(Object[] a, int index) {
        return (E) a[index];
    }

Copy the code

Remove (int index) method

Instead of operating directly on the original array like an ArrayList does, you just create a new array and copy it over.

The problem?

  1. Why do you do that?

    Array objects are allocated in the heap, and the System. Arraycopy operation does not guarantee the integrity of the original array, because there is no assignment (==), so we need to recreate the array, and then call setArray to set the value.

public E remove(int index) {
    final ReentrantLock lock = this.lock;
    lock.lock();
    try {
        Object[] elements = getArray();
        int len = elements.length;
        // Or the index element
        E oldValue = get(elements, index);
       // Ready to start moving elements.
        int numMoved = len - index - 1;
        // This means that in the last position, just copy it
        if (numMoved == 0)
            setArray(Arrays.copyOf(elements, len - 1));
        else {
           // The length of the new array is one less than the previous one because an element is being removed.
           // Split the node into two parts, copy the two parts into an array, and set them back. Notice that we're not operating directly on the original array.
            Object[] newElements = new Object[len - 1];
            System.arraycopy(elements, 0, newElements, 0, index);
            System.arraycopy(elements, index + 1, newElements, index,
                             numMoved);
            setArray(newElements);
        }
        return oldValue;
    } finally{ lock.unlock(); }}Copy the code

Other methods are basically similar, first get the lock, then get the array, normal operation array, as long as remember, as long as the operation involving changes, you need to create a new array, operate on the new array, and then set the new array through the set method.

The problem?

  1. ReentrantLock is volatile every time it is acquired.

    ReentrantLock is implemented by the javaApi, mainly through Park and unPark to block threads, only atomicity is guaranteed.

    Using volatile to modify arrays in CopyOnWriteArrayList ensures memory consistency.

The iterator

Unlike most collections of ArrayList, the iterator of CopyOnWriteArrayList has no quick failure mechanism. That means there are no modCount operations in iterators and CopyOnWriteArrayList. There is no mechanism for checking.

COWIterator iterators do not support remove,set, and add operations, so the examples listed in ArrayList analysis will not fail under CopyOnWriteArrayList scenarios.

  public Spliterator<E> spliterator(a) {
        return Spliterators.spliterator
            (getArray(), Spliterator.IMMUTABLE | Spliterator.ORDERED);
    }

    static final class COWIterator<E> implements ListIterator<E> {
        /** Snapshot of the array */
        private final Object[] snapshot;
        /** Index of element to be returned by subsequent call to next. */
        private int cursor;

        private COWIterator(Object[] elements, int initialCursor) {
            cursor = initialCursor;
            snapshot = elements;
        }

        public boolean hasNext(a) {
            return cursor < snapshot.length;
        }

        public boolean hasPrevious(a) {
            return cursor > 0;
        }

        @SuppressWarnings("unchecked")
        public E next(a) {
            if (! hasNext())
                throw new NoSuchElementException();
            return (E) snapshot[cursor++];
        }

        @SuppressWarnings("unchecked")
        public E previous(a) {
            if (! hasPrevious())
                throw new NoSuchElementException();
            return (E) snapshot[--cursor];
        }

        public int nextIndex(a) {
            return cursor;
        }

        public int previousIndex(a) {
            return cursor-1;
        }

        /** It is clear that this operation is not supported. Throw the exception */ directly
        public void remove(a) {
            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);
            Object[] elements = snapshot;
            final int size = elements.length;
            for (int i = cursor; i < size; i++) {
                @SuppressWarnings("unchecked")E e = (E) elements[i]; action.accept(e); } cursor = size; }}Copy the code

conclusion

  1. ArrayList is a thread-safe alternative class. Use in multithreaded situations.
  2. Iterators to manipulate lists are not supported.
  3. There is no quick failure mechanism
  4. Every time a change is involved, we take the array, copy the new array, and assign it to the new array. usingvolatileTo achieve memory consistency.
  5. Copying arrays consumes memory space, resulting in low memory utilization. So, if you’re reading more and writing less, it’s good to use it. But then again, with lots of writes, it doesn’t feel too bad. There is only one lock, it will not cause multiple threads to copy at the same time, but copy operations in a period of time too much.
  6. CopyOnWriteArraySetIs based onCopyOnWriteArrayListRealization, mainly is to useaddIfAbsentMethods. Find first, if not find, add