04_concurrent container class

1. Reproduce thread unsafe: List

First, use List as the demonstration object, and create multiple threads to perform add operations on ArrayList, a common implementation class of the List interface.

public class NotSafeDemo {

    public static void main(String[] args) {

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

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

Test Results:  

A thread unsafe error has occurred

When ArrayList is modified by multiple threads at the same time, a java.util.ConcurrentModificationException (concurrent modification exception) will be thrown, because the add and other methods of ArrayList are not thread-safe, as evidenced by the source code:

solution:

There are many implementation classes for the List interface, besides the commonly used ArrayList, there are also Vector and SynchronizedList .

They all have the synchronized keyword, indicating that they are all thread-safe.

 

Try using Vector or synchronizedList instead: it can be solved!

public static void main(String[] args) {

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

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

Disadvantages of Vector and Synchronized:

vector: The memory consumption is relatively large , which is suitable for the case of a relatively large increment

SynchronizedList: The code involved in the iterator does not add thread synchronization code  

2. CopyOnWrite container

What is a CopyOnWrite container?

A CopyOnWrite container (COW container for short) is a copy-on-write container. The popular understanding is that when we add elements to a container, we do not directly add to the current container, but first copy the current container to create a new container, and then add elements to the new container. After adding the elements, Then point the reference of the original container to the new container. The advantage of this is that we can perform concurrent reads on the CopyOnWrite container without locking, because the current container will not add any elements. So the CopyOnWrite container is also an idea of ​​reading and writing separation, reading and writing different containers .

Starting from JDK1.5, the Java concurrency package provides two concurrent containers implemented using the CopyOnWrite mechanism, which are CopyOnWriteArrayList and CopyOnWriteArraySet .

Let's take a look at the CopyOnWriteArrayList class first: find that its essence is an array

Let's take a look at the add method of CopyOnWriteArrayList: it is found that this method is thread-safe

Use CopyOnWriteArrayList to transform the main method:  

    public static void main(String[] args) {

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

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

The CopyOnWrite concurrent container is used for concurrent scenarios with more reads and fewer writes . For example: white list, black list. If we have a search website, users enter keywords to search in the search box of this website, but some keywords are not allowed to be searched. These keywords that cannot be searched will be placed in a blacklist, and the blacklist will only be updated once in a certain period.

shortcoming:

  1. Memory usage problem . When writing, new objects will be created and added to the new container, while the objects in the old container are still in use, so there are two copies of object memory. Reduce the memory consumption of large objects by compressing the elements in the container. For example, if the elements are all decimal numbers, you can consider compressing them into 36 or 64. Or instead of using the CopyOnWrite container, use other concurrent containers, such as ConcurrentHashMap.

  2. Data consistency issues . The CopyOnWrite container can only guarantee the final consistency of the data, but cannot guarantee the real-time consistency of the data. So if you want the written data to be read immediately, please don't use the CopyOnWrite container.

3. Extending the analogy: Set and Map

Both HashSet and HashMap are also thread-unsafe, similar to ArrayList, which can also be proven through code.

private static void notSafeMap() {
        Map<String, String> map = new HashMap<>();

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

    private static void notSafeSet() {
        Set<String> set = new HashSet<>();

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

Will report: ConcurrentModificationException exception information.

Collections provides the method synchronizedList to ensure that the list is thread-safe for synchronization. What about Set and Map?

HashMap<String, String> map = new HashMap<>(); //不安全
Hashtable<String, String> map = new Hashtable<>(); //安全
Map<String, String> map = Collections.synchronizedMap(new HashMap<>()); //安全
ConcurrentMap<String, String> map = new ConcurrentMap<>(); //安全

The CopyOnWrite container implementation classes provided by JUC are: CopyOnWriteArrayList and CopyOnWriteArraySet.

Is there any implementation of Map:

The final realization:

public class NotSafeDemo {

    public static void main(String[] args) {
        notSafeList();
        notSafeSet();
        notSafeMap();
    }

    private static void notSafeMap() {
        //Map<String, String> map = new HashMap<>();
        //Map<String, String> map = Collections.synchronizedMap(new HashMap<>());
        Map<String, String> map = new ConcurrentHashMap<>();

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

    private static void notSafeSet() {
        //Set<String> set = new HashSet<>();
        Set<String> set = new CopyOnWriteArraySet<>();

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

    private static void notSafeList() {
        //List<String> list = new Vector<>();
        //List<String> list = Collections.synchronizedList(new ArrayList<>());
        List<String> list = new CopyOnWriteArrayList<>();

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

Extension: What is the underlying data structure of HashSet? HashMap?

But the add of HashSet is to put a value, and the HashMap is to put K and V key-value pairs

4. Concurrent Containers and Synchronous Containers

A synchronization container can be simply understood as a container that achieves synchronization through synchronized . Synchronizing containers will result in serial execution of container method calls in multiple threads, reducing concurrency because they all use the container's own object as a lock. Iterative reading and writing under concurrent conditions are not thread-safe . Such as: classes created by static factory methods of Vector, Stack, HashTable, and Collections (such as Collections.synchronizedList)

Concurrent containers are designed for concurrent access by multiple threads. The concurrent package was introduced in jdk5.0, which provides many concurrent containers, such as ConcurrentHashMap, CopyOnWriteArrayList, etc.

ConcurrentHashMap: Segment structure is used internally, and Hash is used twice for positioning. When writing, only Segment is locked.
CopyOnWriteArrayList: CopyOnWrite copies a new one when writing, modifies on the new one, and then points the reference to the new one. It can only achieve the final consistency of data, not real-time consistency; instead of List, it is suitable for the situation where the read operation is the main

Both synchronous containers and concurrent containers provide appropriate thread safety for multi-threaded concurrent access, but concurrent containers are more scalable.

public static void main(String[]args){
    Vector v = new Vector();
    for (int i = 0; i < 10; i++) {
        int a = i;
        new Thread(()->{
            v.add(a);
        }).start();
    }
    for (Iterator iterator = v.iterator(); iterator.hasNext();) {
        int element = (int) iterator.next();
        v.remove(element);
    }
}

5. Performance testing (understanding)

① Introduce spring-core dependency

<dependency>
    <groupId>org.springframework</groupId>
    <artifactId>spring-core</artifactId>
    <version>5.3.18</version>
</dependency>

② Test code

StopWatch stopwatch = new StopWatch();

public static void main(String[] args) {
    StopWatch stopwatch = new StopWatch();
    List<Integer> list = new Vector<>();
    stopwatch.start("Vector:write数据");
    IntStream.rangeClosed(1,1000000).parallel().forEach( a ->{
        list.add(new Random().nextInt(1000000));
    });
    stopwatch.stop();
    stopwatch.start("Vector:read数据");
    IntStream.rangeClosed(1,1000000).parallel().forEach( a ->{
        list.get(new Random().nextInt(1000000));
    });
    stopwatch.stop();
    System.out.println(stopwatch.prettyPrint());
}

③ Results

Guess you like

Origin blog.csdn.net/qq_45037155/article/details/130408459