CopyOnWriteArrayList is introduced into

Simulating a traditional ArrayList is thread unsafe

public class Demo1 {
    public static void main(String[] args) {
        //List<String> list = new CopyOnWriteArrayList<>();
        List<String> list = new ArrayList<>();

        // Start 50 threads to add data to the ArrayList
        for (int i = 1; i <= 50; i++) {
            new Thread(() -> {
                list.add(UUID.randomUUID().toString().substring(0.5)); System.out.println(list); }, String.valueOf(i)).start(); }}}Copy the code

Fail-fast raises modcount modification exception (modcount is a variable in the source code for ArrayList, since ArrayList is not a collection class designed for concurrency)

How to solve this problem?

The Vector collection is a thread-safe version of ArrayList whose methods are modified with a layer of synchronized, using JVM built-in locks to ensure atomicity, visibility, and order in concurrent cases. But it also brought performance problems, because once synchronized expanded to heavyweight locks, there was a shift from user status to mentality, and multithreaded context switching brought overhead. Another problem is that the expansion of Vector collections is not as good as the ArrayList strategy

List<String> list = new Vector<>();

The second way: use the Collections. SynchronizedList

List<String> list = Collections.synchronizedList(new ArrayList<>());

Method 3: Use the concurrent container provided by JUC, CopyOnWriteArrayList

List<String> list = new CopyOnWriteArrayList<>();

CopyOnWriteArrayList analyses

Like ArrayList, its underlying data structure is an array, with transient to keep it from being serialized and volatile to keep it visible and ordered in multiple threads

Let’s look at the constructor first. Okay

    public CopyOnWriteArrayList(a) {
       // Creates an array of size 0 by default
        setArray(new Object[0]);
    }

    final void setArray(Object[] a) {
        array = a;
    }
	
    public CopyOnWriteArrayList(Collection<? extends E> c) {
        Object[] elements;
        // If the current collection is of type CopyOnWriteArrayList, assign it directly
        if(c.getClass() == CopyOnWriteArrayList.class) elements = ((CopyOnWriteArrayList<? >)c).getArray();else {
         	// Otherwise call toArra() to turn it into an array
            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); }// Set the array
        setArray(elements);
    }
	
    public CopyOnWriteArrayList(E[] toCopyIn) {
        // Copy the array element passed in to the current array
        setArray(Arrays.copyOf(toCopyIn, toCopyIn.length, Object[].class));
    }
	
Copy the code

When we look at the operation of reading data, we can see that none of the operations are locked, which is strange. How to ensure thread safety?

    final Object[] getArray() {
        return array;
    }
    public int size(a) {
        return getArray().length;
    }
   public boolean isEmpty(a) {
        return size() == 0;
    }
    public int indexOf(E e, int index) {
        Object[] elements = getArray();
        return indexOf(e, elements, index, elements.length);
    }
    public int lastIndexOf(Object o) {
        Object[] elements = getArray();
        return lastIndexOf(o, elements, elements.length - 1); }...Copy the code

Take a look at the add function when it changes

    public boolean add(E e) {
        // Use ReentrantLock to lock
        final ReentrantLock lock = this.lock;
        lock.lock();
        try {
            // Call getArray() to get the original array
            Object[] elements = getArray();
            int len = elements.length;
            // Copy the old array to get an array of length +1
            Object[] newElements = Arrays.copyOf(elements, len + 1);
            // Add an element and replace the array with the setArray() function
            newElements[len] = e;
            setArray(newElements);
            return true;
        } finally{ lock.unlock(); }}Copy the code

It can be seen that the modification operation is based on the Fail-safe mechanism. Like our String, we do not directly operate on the original object, but copy it to modify it. In addition, the modification operation here uses the Lock Lock, so it ensures the thread safety problem.

Let’s take a look at the remove operation and see if that’s what we’re doing

    public boolean remove(Object o) {
        Object[] snapshot = getArray();
        int index = indexOf(o, snapshot, 0, snapshot.length);
        return (index < 0)?false : remove(o, snapshot, index);
    }

    private boolean remove(Object o, Object[] snapshot, int index) {
        final ReentrantLock lock = this.lock;
        / / lock
        lock.lock();
        try {
            Object[] current = getArray();
            int len = current.length;
            if(snapshot ! = current) findIndex: {int prefix = Math.min(index, len);
                for (int i = 0; i < prefix; i++) {
                    if(current[i] ! = snapshot[i] && eq(o, current[i])) { index = i;breakfindIndex; }}if (index >= len)
                    return false;
                if (current[index] == o)
                    break findIndex;
                index = indexOf(o, current, index, len);
                if (index < 0)
                    return false;
            }
            // Copy an array
            Object[] newElements = new Object[len - 1];
            System.arraycopy(current, 0, newElements, 0, index);
            System.arraycopy(current, index + 1,
                             newElements, index,
                             len - index - 1);
            // Replace the original array
            setArray(newElements);
            return true;
        } finally{ lock.unlock(); }}Copy the code

Comparing with ArrayList, we can see that its efficiency is much lower than ArrayList. After all, in multi-threaded scenarios, it needs to copy a copy of the original array each time, which consumes memory and time, while ArrayList just expands when its capacity is full. So use ArrayList for non-multithreaded scenarios.

So that’s why I’m learning ArrayList, because the JUC version of CopyOnWriteArrayList can do things that an ArrayList can’t, so let’s just use CopyOnWriteArrayList.

summary

  • CopyOnWriteArrayList is suitable for multi-threaded scenarios. It adopts the idea of read-write separation. The read operation is not locked, while the write operation is locked, and the write operation is inefficient
  • CopyOnWriteArrayList is fail-safe. Each change will be copied and replaced after the change
  • CopyOnWriteArrayList uses ReentrantLock for locking.