What if the List set is unsafe under the condition of multithreading concurrency? The new colleague was directly confused by me

Posted by gdboling on Thu, 10 Feb 2022 13:19:36 +0100

preface

Some time ago, a new employee came to the company. He said that the new employee had been outsourcing code for one and a half years before he came to our company. Once I chatted with him about List. I casually asked him how to solve the unsafe multi-threaded concurrency of List collection when outsourcing. Unexpectedly, he looked at me in a confused circle and said whether it was safe? I was speechless and asked him what he was doing after a year and a half. It was not clear. He said that before outsourcing, he was just fooling around almost every day. Only when he felt that he couldn't go on like this would he want to leave his job and change to a better job. I can only say that fortunately, he "woke up" early

In the daily development process, List is a common set. For example, the return value ratio of query database content will be loaded with a set, but will there be security problems under the condition of multithreading concurrency? If there is a problem, let's test and solve it

 

1, The List collection uses simulated concurrency tests

1. Under single thread environment

public static void main(String[] args) {
    // List collection
    List<String> list = new ArrayList<>();
    // Circular insertion
    for (int i = 0; i < 10; i++) {
        list.add(UUID.randomUUID().toString().substring(0,5));
        System.out.println(list);
    }
}

 

We can see that under the condition of single thread, we have no problem in inserting the list. Let's simulate the execution under the condition of concurrency.

2 multithreading environment

public static void main(String[] args) {
    // List collection
    List<String> list = new ArrayList<>();
    // Circular insertion
    for (int i = 0; i < 10; i++) {
        // Start thread execution
        new Thread(()->{
            list.add(UUID.randomUUID().toString().substring(0,5));
            System.out.println(list);
        },"thread  List").start();

    }
}

 

If the ArrayList is modified at the same time during iteration, it will throw Java util. The concurrent modificationexception is a concurrent modification exception.

 

2, Solution

1. Use Vector class

public static void main(String[] args) {
    // List collection
    List<String> list = new Vector<>();
    // Circular insertion
    for (int i = 0; i < 10; i++) {
        // Start thread execution
        new Thread(()->{
            list.add(UUID.randomUUID().toString().substring(0,5));
            System.out.println(list);
        },"thread  List").start();

    }
}

Vector is accessed synchronously. The bottom layer of its add method is decorated with the synchronized keyword.

 

Test results:

 

2. Use collections synchronizedList

public static void main(String[] args) {
   // List collection
    List<String> list = Collections.synchronizedList(new ArrayList<>());
    // Circular insertion
    for (int i = 0; i < 10; i++) {
        // Start thread execution
        new Thread(()->{
            list.add(UUID.randomUUID().toString().substring(0,5));
            System.out.println(list);
        },"thread  List").start();

    }
}

Looking at the underlying source code, you can find that it also uses the synchronized keyword.

 

3. Use the concurrent container CopyOnWriteArrayList

public static void main(String[] args) {
    // List collection
    List<String> list = new CopyOnWriteArrayList<>();
    // Circular insertion
    for (int i = 0; i < 10; i++) {
        // Start thread execution
        new Thread(()->{
            list.add(UUID.randomUUID().toString().substring(0,5));
            System.out.println(list);
        },"thread  List").start();

    }
}

Check the source code, which uses the lock lock mechanism.

 

Copy when writing. When multiple threads call, copy a copy when writing to avoid data problems caused by overwriting. When writing, the original set is not modified, but a new copy is copied. After modification, the pointer is moved.

From jdk1 5 starts Java and provides two concurrent containers implemented by CopyOnWrite mechanism in the contract. They are CopyOnWriteArrayList and CopyOnWriteArraySet. The CopyOnWrite container is very useful and can be used in many concurrent scenarios.

Interpretation of source code:

/**
 * Appends the specified element to the end of this list.
 *
 * @param e element to be appended to this list
 * @return {@code true} (as specified by {@link Collection#add})
 */
public boolean add(E e) {
    final ReentrantLock lock = this.lock;//Reentrant lock
    lock.lock();//Lock
    try {
        Object[] elements = getArray();
        int len = elements.length;
        Object[] newElements = Arrays.copyOf(elements, len + 1);//Copy new array
        newElements[len] = e;
        setArray(newElements);//Point reference to new array
        return true;
    } finally {
        lock.unlock();//Unlock
    }
}

add() adds a lock when adding a collection to ensure synchronization and avoid copying N copies when multiple threads write.

 

summary

Usage scenario of CopyOnWriteArrayList: more reading and less writing (blacklist, whitelist, access and update scenarios of commodity categories), and the collection is small. Therefore, generally speaking, we will use the thread safe container provided to us under the JUC package instead of the old generation of thread safe containers.

 

Topics: Java jvm Multithreading Concurrent Programming set