Java - JUC high concurrency programming, interview must ask (thread safety of collection)

Keywords: Java Interview set JUC

4 thread safety of collections

4.1 set operation Demo (demonstration)

NotSafeDemo:

import java.util.ArrayList;
import java.util.List;
import java.util.UUID;
/*** Collection thread safety case
*/
public class NotSafeDemo {
/**
* Multiple threads modify the collection at the same time
*/
    public static void main(String[] args) {
        List list = new ArrayList();
        for (int i = 0; i < 100; i++) {
           new Thread(() ->{
             list.add(UUID.randomUUID().toString());
             System.out.println(list);
         }, "thread " + i).start();
       }
   }
}

Result: abnormal content
java.util.ConcurrentModificationException

Question: why do concurrent modification exceptions occur?
View the source code of the add method of ArrayList

/**
* Appends the specified element to the end of this list.
*
* @param e element to be appended to this list
* @return <tt>true</tt> (as specified by {@link Collection#add})
*/
public boolean add(E e) {
   ensureCapacityInternal(size + 1); // Increments modCount!!
   elementData[size++] = e;
   return true;
}

So how do we solve the thread safety problem of List type?

4.2 Vector

Vector is a vector queue, which is a class added in JDK1.0. It inherits from AbstractList and implements List, randomaccess and clonable interfaces. Vector inherits AbstractList and implements List; Therefore, it is a queue that supports related functions such as addition, deletion, modification and traversal. Vector implements RandmoAccess interface, which provides random access function.

RandmoAccess is implemented by List in java to provide fast access for List. In vector, we can quickly obtain the element object through the element serial number; This is fast random access. Vector implements the clonable interface, that is, the clone() function. It can be cloned.

Unlike ArrayList, operations in Vector are thread safe.

NotSafeDemo code modification:

import java.util.ArrayList;
import java.util.List;
import java.util.UUID;
import java.util.Vector;
/**
* Collection thread safety case
*/
public class NotSafeDemo {
/**
* Multiple threads modify the collection at the same time
*/
  public static void main(String[] args) {
      List list = new Vector();
      for (int i = 0; i < 100; i++) {
          new Thread(() ->{
            list.add(UUID.randomUUID().toString());System.out.println(list);
          }, "thread " + i).start();
      }
   }
}

The result is no exception.

View the add method of Vector

/**
* Appends the specified element to the end of this Vector.
*
* @param e element to be appended to this Vector
* @return {@code true} (as specified by {@link Collection#add})
* @since 1.2
*/
public synchronized boolean add(E e) {
   modCount++;
   ensureCapacityHelper(elementCount + 1);
   elementData[elementCount++] = e;
   return true;
 }

Found that the add method is synchronized, thread safe! Therefore, there are no concurrent exceptions.

4.3 Collections

Collections provides the method synchronizedList to ensure that the list is thread safe.

NotSafeDemo code modification

import java.util.*;
/**
* Collection thread safety case
*/public class NotSafeDemo {
/**
* Multiple threads modify the collection at the same time
*/
   public static void main(String[] args) {
       List list = Collections.synchronizedList(new ArrayList<>());
       for (int i = 0; i < 100; i++) {
           new Thread(() ->{
               list.add(UUID.randomUUID().toString());
               System.out.println(list);
           }, "thread " + i).start();
      }
   }
}

There were no exceptions.

View method source code

/**
* Returns a synchronized (thread-safe) list backed by the specified
* list. In order to guarantee serial access, it is critical that
* <strong>all</strong> access to the backing list is accomplished
* through the returned list.<p>
*
* It is imperative that the user manually synchronize on the returned
* list when iterating over it:
* <pre>
* List list = Collections.synchronizedList(new ArrayList());
* ...
* synchronized (list) {
* Iterator i = list.iterator(); // Must be in synchronized block
* while (i.hasNext())
* foo(i.next());
* }
* </pre>
* Failure to follow this advice may result in non-deterministic behavior.*
* <p>The returned list will be serializable if the specified list is
* serializable.
*
* @param <T> the class of the objects in the list
* @param list the list to be "wrapped" in a synchronized list.
* @return a synchronized view of the specified list.
*/
public static <T> List<T> synchronizedList(List<T> list) {
    return (list instanceof RandomAccess ?
    new SynchronizedRandomAccessList<>(list) :
    new SynchronizedList<>(list));
}

4.4 copyonwritearraylist (key)

First, we will learn about CopyOnWriteArrayList. Its features are as follows:
It is equivalent to a thread safe ArrayList. Like ArrayList, it is a variable array; However, unlike ArrayList, it has the following features:

  1. It is most suitable for applications with the following characteristics: the List size is usually kept small, read-only operations are far more than variable operations, and inter thread conflicts need to be prevented during traversal.

  2. It is thread safe.

  3. Because you usually need to copy the entire underlying array, variable operations (add(), set(), remove(), and so on) are expensive.

  4. Iterators support immutable operations such as hasNext(), next(), but do not support immutable operations such as remove().

  5. Traversal using iterators is fast and does not conflict with other threads. When constructing iterators, iterators rely on invariant array snapshots.

  6. Low efficiency of exclusive lock: it is solved by the idea of separation of read and write

  7. The write thread obtains the lock, and other write threads are blocked

  8. Copy ideas:

When we add elements to a container, instead of directly adding them to the current container, we first Copy the current container, Copy a new container, and then add elements to the new container. After adding elements, we point the reference of the original container to the new container.

At this time, a new problem will be thrown out, that is, the problem of inconsistent data. If the write thread has not had time to write into memory, other threads will read dirty data.

This is the idea and principle of CopyOnWriteArrayList. Just a copy.

NotSafeDemo code modification

import java.util.*;
import java.util.concurrent.CopyOnWriteArrayList;
/**
* Collection thread safety case
*/
public class NotSafeDemo {
/**
* Multiple threads modify the collection at the same time
*/
    public static void main(String[] args) {
       List list = new CopyOnWriteArrayList();
       for (int i = 0; i < 100; i++) {
           new Thread(() ->{
                list.add(UUID.randomUUID().toString());
                System.out.println(list);
            }, "thread " + i).start();
       }
    }
}

The result is still no problem.

**Cause analysis (key points) 😗* Dynamic array and thread safety

Next, the principle of CopyOnWriteArrayList is further explained from the two aspects of "dynamic array" and "thread safety".

Dynamic array mechanism

  • It has a "volatile array" inside to hold data. When "adding / modifying / deleting" data, a new array will be created, and the updated data will be copied to the new array. Finally, the array will be assigned to "volatile array", which is why it is called CopyOnWriteArrayList
  • Because it creates new arrays when "adding / modifying / deleting" data, CopyOnWriteArrayList is inefficient when it involves modifying data; However, it is more efficient to perform only traversal search.

"Thread safety" mechanism

  • Implemented through volatile and mutex.
  • Use the "volatile array" to save the data. When a thread reads the volatile array, it can always see the last write of the volatile variable by other threads; In this way, volatile provides the guarantee of the mechanism that "the data read is always up-to-date".
  • Protect data through mutexes. When "adding / modifying / deleting" data, you will first "obtain the mutex", and then update the data to the "volatile array" after modification, and then "release the mutex", so as to achieve the purpose of protecting data.

4.5 summary (key points)

1. Thread safe and thread unsafe collection
There are two types of collection types: thread safe and thread unsafe, such as:
ArrayList ----- Vector
HashMap -----HashTable
However, the above are implemented through the synchronized keyword, which is inefficient

2. Thread safe collections built by collections

3.java.util.concurrent and contract the CopyOnWriteArrayList CopyOnWriteArraySet type, which is installed with the thread through a dynamic array
Ensure thread safety in all aspects.

Posted by IsmAvatar on Wed, 20 Oct 2021 23:08:42 -0700