The JUC Series of Java Interviews: Examples of Insecure Collection Threads

Examples of unsafe collection threads

Preface

1. When we execute the following statement, what operation did the bottom layer perform?

new ArrayList<Integer>();

An empty array is created at the bottom layer, with an initial value of 10

When the add method is executed, if it exceeds 10, the expansion will be carried out. The expansion size is half of the original value, which is 5, use the following method to expand

Arrays.copyOf(elementData, netCapacity)

Single-threaded environment

There is no problem with ArrayList in a single-threaded environment

public class ArrayListNotSafeDemo {
    
    
    public static void main(String[] args) {
    
    

        List<String> list = new ArrayList<>();
        list.add("a");
        list.add("b");
        list.add("c");

        for(String element : list) {
    
    
            System.out.println(element);
        }
    }
}

Multithreaded environment

Why is ArrayList thread unsafe? Because when writing operations, in order to ensure concurrency, the method does not add synchronized modification, so when writing concurrently, there will be problems

image-20200312202720715

When we start 30 threads at the same time to operate List

/**
 * 集合类线程不安全举例
 */
public class ArrayListNotSafeDemo {
    
    
    public static void main(String[] args) {
    
    

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

        for (int i = 0; i < 30; i++) {
    
    
            new Thread(() -> {
    
    
                list.add(UUID.randomUUID().toString().substring(0, 8));
                System.out.println(list);
            }, String.valueOf(i)).start();
        }
    }
}

An error occurred at this time, which is java.util.ConcurrentModificationException

image-20200312205142763

This exception is a concurrent modification exception

solution

Option 1: Vector

The first method is to use Vector instead of the unsafe List implementation class like ArrayList, which is thread-safe

Regarding how Vector achieves thread safety, it adds a lock to the method, that is, synchronized

image-20200312210401865

In this way, only one thread can operate at a time, so there will be no thread insecurity problems, but because of the lock, the concurrency is reduced based on

Solution 2: Collections.synchronized()

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

Use Collections collection tool class, wrap a layer of synchronization mechanism outside ArrayList

Option 3: Use the method in JUC

CopyOnWriteArrayList: Copy-on-write, mainly a thought of separation of reading and writing

Copy-on-write, the CopyOnWrite container is the container that is copied on-write. When adding elements to a container, it does not directly add to the current container Object[], but first copies Object[] to create a new container object[ ] newElements, and then add the original in the new container Object[] newElements. After adding the elements, point the reference of the original container to the new container setArray(newElements); the advantage of this is that the copyOnWrite container can be concurrent, and No need to lock, because the current container does not need to add any elements. Therefore, the CopyOnWrite container is also an idea of ​​separation of reading and writing. Reading and writing are in different containers.

That is, when writing, expand the ArrayList by one, then fill in the value, and notify other threads that the reference of the ArrayList points to the expanded

View the source code of the underlying add method

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

First need to lock

final ReentrantLock lock = this.lock;
lock.lock();

Then expand a unit at the end

Object[] elements = getArray();
int len = elements.length;
Object[] newElements = Arrays.copyOf(elements, len + 1);

Then fill in the content that needs to be added in the expanded space

newElements[len] = e;

Finally set the content to the Array

HashSet thread is not safe

CopyOnWriteArraySet

The bottom layer still uses CopyOnWriteArrayList for instantiation

image-20200312221602095

HashSet underlying structure

Similarly, the underlying structure of HashSet is HashMap

image-20200312221735178

But why do I call the HashSet.add() method and only need to pass one element, while HashMap needs to pass key-value key-value pairs?

First we look at the add method of hashSet

    public boolean add(E e) {
    
    
        return map.put(e, PRESENT)==null;
    }

We can find that when we call add, we store a value into the map, which is only stored as a key, while value stores a constant of type Object, which means that HashSet only cares about the key, not the value.

HashMap thread is not safe

Similarly, HashMap is not safe in a multi-threaded environment

    public static void main(String[] args) {
    
    

        Map<String, String> map = new HashMap<>();

        for (int i = 0; i < 30; i++) {
    
    
            new Thread(() -> {
    
    
                map.put(Thread.currentThread().getName(), UUID.randomUUID().toString().substring(0, 8));
                System.out.println(map);
            }, String.valueOf(i)).start();
        }
    }

Solution

1、使用Collections.synchronizedMap(new HashMap<>());

2. Use ConcurrentHashMap

Map<String, String> map = new ConcurrentHashMap<>();

Guess you like

Origin blog.csdn.net/weixin_43314519/article/details/110195646