Thread safe List

   we all know that ArrayList is non thread safe. When multithreading development, if multiple threads operate on the same ArrayList, a ConcurrentModificationException error will be reported. At this time, we need a thread safe List set.
  I encountered such problems in the development process. A thread safe List collection is required for MQ message processing

Vector container

Vector is a thread safe container. Because it adds the synchronized keyword to almost every method declaration to make the container safe

Static method sychronizedlist (list) of Collections

Syscronizedlist belongs to

java.util.Collections.SynchronizedList(List<T> list)

Syscronizedlist can convert all the implementation classes of the List interface into a thread safe List set, which has better scalability and compatibility than Vector. If using collections Synchronizedlist (New arraylist()) makes ArrayList thread safe. Almost every method adds the synchronized keyword, but it is not added to the declaration of the method, but inside the method. The construction method of syscronizedlist is as follows

final List<E> list;

SynchronizedList(List<E> list) {
    super(list);
    this.list = list;
}

The source code of some methods of syscronizedlist is as follows

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

It can be seen that all methods of syscronizedlist are locked (that is, reading and writing are locked). When the amount of data is large, and there are more reading and less writing, the performance of syscronizedlist is very poor. Therefore, in some scenes, he is not the best choice. The following container can be used when the read operation is much larger than the write operation

CopyOnWrite container

CopyOnWrite (hereinafter referred to as COW): copy and then write, that is, when adding elements, first copy the original container and then add new elements.
Concurrent classes in two concurrent packages

java.util.concurrent.CopyOnWriteArrayList
java.util.concurrent.CopyOnWriteArraySet

CopyOnWrite collection classes are just these two. Java 1.5 began to add them

CopyOnWriteArrayList

add method source code

public boolean add(E e) {
    //Get reentry lock
    final ReentrantLock lock = this.lock;
    //Lock
    lock.lock();
    try {
        //Get the old array and get the length of the old array
        Object[] elements = getArray();
        int len = elements.length;
        //Copy the elements of the old array to the new array, and add 1 to the original size
        Object[] newElements = Arrays.copyOf(elements, len + 1);
        //Insert value into new array
        newElements[len] = e;
        //Replace old array with new array
        setArray(newElements);
        return true;
    } finally {
        //Release lock
        lock.unlock();
    }
}

From the source code, we can see that the re-entry lock is used in the add operation, but this lock is only for write write operations. Why there is no mutual exclusion between reading and writing? The key is that the operation of adding values is not directly completed in the original array. Instead, use the original array to copy a new array, then insert the values into the new array, and finally replace the old array with the new array. In this way, the insertion is completed. In this way, the old array is not modified during the add process, so the write operation does not affect the read operation. In addition, the array defines a private transient volatile Object[] array, in which volatile modification is adopted to ensure memory visibility, and the read thread can immediately know this modification. Let's take a look at the read operation

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

The read operation does not use any synchronization control or locking at all, because the internal structure of the array will not change, so the read is thread safe.

The advantage of this is that in the case of high concurrency, there is no need to lock when reading elements and lock when writing data, which greatly improves the reading performance.

CopyOnWriteArraySet
The CopyOnWriteArraySet logic is simpler. It uses the addIfAbsent method of CopyOnWriteArrayList to remove duplicates. When adding elements, judge whether the object already exists. If it does not exist, it will be added to the collection.

/**
 * Appends the element, if not present.
 *
 * @param e element to be added to this list, if absent
 * @return {@code true} if the element was added
 */
public boolean addIfAbsent(E e) {
    Object[] snapshot = getArray();
    return indexOf(e, snapshot, 0, snapshot.length) >= 0 ? false :
        addIfAbsent(e, snapshot);
}

**These two kinds of concurrent sets are only suitable for the situation of more reading and less writing. If more writing and less reading, it is meaningless to use this, because each write operation requires collection memory replication, which has a large performance cost. If the set is large, it is easy to cause memory overflow.

summary

According to our business needs, it is recommended to use the first method when the insertion operation is far more than reading. This is because CopyOnWriteArrayList will create a new array during the insertion process, which will consume a lot of memory when the amount of data is particularly large. Of course, if the read operation is much larger than the insertion, the second method must be more advantageous. After all, the read operation does not need to be locked at all.

Keywords: Java security list

Added by jack_wetson on Tue, 08 Feb 2022 06:17:39 +0200