Know the queues in Java: Vector, ArrayList, CopyOnWriteArrayList, SynchronizedList

Know the queues in Java: Vector, ArrayList, CopyOnWriteArrayList, SynchronizedList

Hello, I am watching the mountains.

Book connected to Wen, last chatted use in multiple threads ArrayList what will happen , this time we usually talk about common list: Vector, ArrayList, CopyOnWriteArrayList, SynchronizedList .

Vector

VectorIt is provided in JDK 1.0, although it is not marked Deprecated, in fact no one uses it anymore. The main reason is poor performance and does not meet demand.

As you can see from the source code (the source code is not posted here), it Vectoris based on an array implementation. Almost all operation methods use synchronizedkeywords to achieve method synchronization. This synchronization method can lock a single operation, such as multiple threads at the same time Execution addwill block execution synchronously, but multi-threaded execution addand removetime will not block.

However, in most scenarios where you need to lock the queue, you want to lock the entire queue, not just a single operation. In other words, Vectorit is different from our expectation, but it also increases the performance overhead caused by the synchronization operation. Therefore, it is not necessary to use the scene, you can use it ArrayListinstead, even if it is a multi-threaded situation that requires a synchronized queue, you can also use CopyOnWriteArrayListand SynchronizedListreplace it.

ArrayList

ArrayListIt is provided in JDK 1.1. As Vectorthe successor ( ArrayListimplementation is Vectoralmost identical), all ArrayListmethods synchronizedare removed, synchronization is not achieved at all, and it is not thread-safe.

Its non-thread safety is also reflected in the rapid failure of iterators. After using the method iteratorand listIteratorcreating the iterator, if the original ArrayListqueue is modified (add or remove), ConcurrentModificationExceptionan exception will be reported when the iterator is iterating . It can be seen from the source code that during the iteration process of the iterator, it will check whether the modCountnumber of modifications in the queue expectedModCountis equal to the snapshot of the number of modifications that fell when the iterator was created . Equality means no modification. The code is as follows:

private class Itr implements Iterator<E> {
    
    
    // 这段代码是从 ArrayList 中摘取的
    // 只留下检查方法,略过其他代码,有兴趣的可以从源码中查看
    final void checkForComodification() {
    
    
        if (modCount != expectedModCount)
            throw new ConcurrentModificationException();
    }
}

The third point is that in a multi-threaded scenario, adding elements may lose data, or an array out-of-bounds exception may occur. What happens when ArrayList is used in multi-threading is described in detail, so I won’t go into details here.

SynchronizedList

SynchronizedListYes, Collectionsa static inner class, Collections.synchronizedList()created using a static method, is a Listencapsulated implementation realized by a composite class. Most of its methods use synchronized (mutex){...}code block synchronization, because the locked object mutexis the same object defined in the queue object, so mutexwhen the lock is locked, Vectorthe entire queue can be locked, which solves the problem of not being able to lock the entire queue. . So if there are multiple threads operating addand removemethods at the same time , it will block synchronous execution.

ArrayListThe rapid failure of the iterator in the existing situation still exists, as the note in the source code below: If you want to use the iterator, you need to manually implement synchronization.

static class SynchronizedList<E>
    extends SynchronizedCollection<E>
    implements List<E> {
    
    
    
    // 代码摘自 Collections,省略很多代码

    public void add(int index, E element) {
    
    
        synchronized (mutex) {
    
    list.add(index, element);}
    }

    public ListIterator<E> listIterator() {
    
    
        return list.listIterator(); // Must be manually synched by user
    }

    public ListIterator<E> listIterator(int index) {
    
    
        return list.listIterator(index); // Must be manually synched by user
    }
}

You need to pay attention to manual synchronization. Since we are concerned about global synchronization, when setting synchronization in the iterator, ensure that the locked object addis the same as the object in the method. This will be explained in the follow-up, so it will not be expanded here.

CopyOnWriteArrayList

CopyOnWriteArrayListIt is provided since JDK 1.5, first look at addthe source code of the method:

public class CopyOnWriteArrayList<E>
    implements List<E>, RandomAccess, Cloneable, java.io.Serializable {
    
    

    /** The lock protecting all mutators */
    final transient ReentrantLock lock = new ReentrantLock();

    /** The array, accessed only via getArray/setArray. */
    private transient volatile Object[] array;

    // 代码摘自 CopyOnWriteArrayList,省略很多代码

    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();
        }
    }

    public boolean addAll(Collection<? extends E> c) {
    
    
        Object[] cs = (c.getClass() == CopyOnWriteArrayList.class) ?
            ((CopyOnWriteArrayList<?>)c).getArray() : c.toArray();
        if (cs.length == 0)
            return false;
        final ReentrantLock lock = this.lock;
        lock.lock();
        try {
    
    
            Object[] elements = getArray();
            int len = elements.length;
            if (len == 0 && cs.getClass() == Object[].class)
                setArray(cs);
            else {
    
    
                Object[] newElements = Arrays.copyOf(elements, len + cs.length);
                System.arraycopy(cs, 0, newElements, len, cs.length);
                setArray(newElements);
            }
            return true;
        } finally {
    
    
            lock.unlock();
        }
    }

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

    /**
     * {@inheritDoc}
     *
     * @throws IndexOutOfBoundsException {@inheritDoc}
     */
    public E get(int index) {
    
    
        return get(getArray(), index);
    }
}

It can be seen that CopyOnWriteArrayListwith the help ReentrantLockof synchronization, the performance is higher synchronizedbefore optimization . It is also implemented through an array, but a keyword is added in front of the array , which realizes the visibility of the array in the case of multi-threading, and is safer. The more important point is that when adding elements, the implementation is to rebuild the array object and replace the original array reference. Compared with the expansion method, the space is reduced, but the performance overhead of the assignment array is also increased. When the element is acquired, there is no lock, and the data is returned directly.ReentrantLocksynchronizedCopyOnWriteArrayListvolatileCopyOnWriteArrayListaddArrayListget

CopyOnWriteArrayListThe iterator of is COWIteratorimplemented, iteratorwhen the method is called , the snapshot of the array in the current queue is assigned to the array reference in the iterator. If the original queue is modified, the array in the queue will point to other references, and the array in the iterator will not change, so in the process of multi-threaded execution, the data in the queue can also be modified by traversing the array through the iterator. While ensuring thread safety in this way, data inconsistencies may also occur, and you can only pay more attention to the use of it.

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;
    }
}

Compare CopyOnWriteArrayList and SynchronizedList

CopyOnWriteArrayListSynchronizedListBoth and both are synchronized, and different strategies are adopted in the implementation method, and their respective focuses are different.

CopyOnWriteArrayListFocus on the separation of reads and writes. When a data write operation ( addor remove) occurs, a lock will be added. Each thread will block the execution. The execution process will create a data copy and replace the object reference; if there is a read operation ( getor iterator) at the same time , the read operation reads Old data may become historical data snapshots or cached data. This will cause data inconsistency when reading and writing occur at the same time, but the data will eventually be consistent. This method is almost the same as the database read-write separation mode, and many features can be compared.

SynchronizedListFocus on strong data consistency, that is, when a data write operation ( addor remove) occurs, a lock will be added, each thread will block the execution, and the operation will also be blocked by the same lock get.

From CopyOnWriteArrayListand SynchronizedListtwo different matters, it can be concluded that CopyOnWriteArrayListthe implementation of high efficiency in writing less reading, more of the scene, SynchronizedListread and write operations efficiency is very balanced, so write to read more, write once read many little scenes efficiency will be higher CopyOnWriteArrayList. Borrow the test results from the Internet:

Compare CopyOnWriteArrayList and SynchronizedList

Summary at the end of the article

  1. synchronizedThe performance of keywords is relatively poor before JDK 8. You can see the synchronization code implemented after JDK 1.5, many of which are ReentrantLockimplemented.
  2. In multi-threaded scenarios, in addition to synchronization, data visibility also needs to be considered, which can be achieved through volatilekeywords.
  3. ArrayListThere is no synchronization operation at all, and it is not thread-safe
  4. CopyOnWriteArrayListAnd SynchronizedListbelong to the thread-safe queue
  5. CopyOnWriteArrayListRealize the separation of reading and writing, suitable for scenarios where write less and read more
  6. SynchronizedListThe data is required to be strongly consistent, which is a global locking method for the queue, and the read operation will also be locked
  7. VectorIt's just that the iterator traversal performance is very poor. If you don't consider the global lock queue, the performance of pure read operations and individual write operations SynchronizedListis not much different.

reference

  1. Why is Java Vector (and Stack) class considered obsolete or deprecated?
  2. CopyOnWriteArrayList and Collections.synchronizedList performance comparison
  3. Collections.synchronizedList, CopyOnWriteArrayList, Vector introduction, source code analysis and performance comparison

Recommended reading

  1. What happens if you have to use ArrayList in multiple threads?

Hello, I am Kanshan, public account: Kanshan’s Lodge, a 10-year-old backend, open source contributors to Apache Storm, WxJava, and Cynomys. Main job: programmer, part-time job: architect. Swim in the code world, enjoy life in drama.

Personal homepage: https://www.howardliu.cn
Personal blog post: Know the queues in Java: Vector, ArrayList, CopyOnWriteArrayList, SynchronizedList
CSDN homepage: http://blog.csdn.net/liuxinghao
CSDN blog post: Know the queues in Java : Vector, ArrayList, CopyOnWriteArrayList, SynchronizedList

Public number: Watching the mountain hut

Guess you like

Origin blog.csdn.net/conansix/article/details/113780875