Thread security issues for JUC collections

Posted by SaxMan101 on Fri, 10 Sep 2021 18:13:05 +0200

scene

Because when working, you don't over-consider the issue of high concurrency. Most collections use normal lists, sets, maps. There's not much problem either. But if you're in a multi-threaded scenario where multiple threads are manipulating a collection at the same time, there's a lot of problem.

Collection Security Issue Code Display

List

Here's a piece of code

      List<String> list = new ArrayList<>();
        for (int i = 0; i < 300; i++) {
            new Thread(() -> {
                list.add(UUID.randomUUID().toString().substring(10));
                System.out.println(list);
            }).start();
        }

This is a partial execution result

java.util.ConcurrentModificationException
	at java.util.ArrayList$Itr.checkForComodification(ArrayList.java:911)
	at java.util.ArrayList$Itr.next(ArrayList.java:861)
	at java.util.AbstractCollection.toString(AbstractCollection.java:461)
	at java.lang.String.valueOf(String.java:2994)
	at java.io.PrintStream.println(PrintStream.java:821)
	at com.anxin.juc.listThread.ListThread.lambda$main$0(ListThread.java:20)
	at java.lang.Thread.run(Thread.java:748)
java.util.ConcurrentModificationException
	at java.util.ArrayList$Itr.checkForComodification(ArrayList.java:911)
	at java.util.ArrayList$Itr.next(ArrayList.java:861)
	at java.util.AbstractCollection.toString(AbstractCollection.java:461)
	at java.lang.String.valueOf(String.java:2994)
	at java.io.PrintStream.println(PrintStream.java:821)
	at com.anxin.juc.listThread.ListThread.lambda$main$0(ListThread.java:20)
	at java.lang.Thread.run(Thread.java:748)
java.util.ConcurrentModificationException

For ConcurrentModificationException exceptions, which means concurrent modification exceptions, this exception pops up when we print this collection, including sometimes when we delete while traversing, which is resolved by an iterator. Drag away.
Then how to solve this problem in JUC. There are almost three solutions.

1. Using Vector Collection

 List<String> list = new Vector<>();
        for (int i = 0; i < 300; i++) {
            new Thread(() -> {
                list.add(UUID.randomUUID().toString().substring(10));
                System.out.println(list);
            }).start();
        }

This Vector is a thread-safe, single-threaded collection. It solves this problem. Almost all of his methods are decorated with synchronous locks. But this has not been used since I worked in this field, and is obsolete because of its low performance. And synchronous locks are not really used in some very concurrent scenarios.

2. Use Collections.synchronizedList

List<String> list = Collections.synchronizedList(new ArrayList<>());
        for (int i = 0; i < 300; i++) {
            new Thread(() -> {
                list.add(UUID.randomUUID().toString().substring(10));
                System.out.println(list);
            }).start();
        }

The source code is as follows:

 public static <T> List<T> synchronizedList(List<T> list) {
	return (list instanceof RandomAccess ?
                new SynchronizedRandomAccessList<T>(list) :
                new SynchronizedList<T>(list));
    }

It is not difficult to explain why he is thread safe.

3. Use CopyOnWriteArrayList

  List<String> list =new CopyOnWriteArrayList();
        for (int i = 0; i < 300; i++) {
            new Thread(() -> {
                list.add(UUID.randomUUID().toString().substring(10));
                System.out.println(list);
            }).start();
        }

This is mainly achieved by write-time replication technology.
Let's look at a code and guess if it will go wrong?

  List<String> list =new CopyOnWriteArrayList();
        list.add("111");
        list.add("222");
        list.add("333");
        list.add("444");
        list.add("555");
        for (String s : list) {
            list.remove(s);
            System.out.println(list);
        }

No error! Why?
because
When we add elements to a container, instead of directly adding them to the current container, we first Copy the current container, duplicate a new container, then add elements to the new container, and then point the reference of the original container to the new container.

A new problem, data inconsistency, is thrown. If the writing thread has not had time to write in memory, other threads will read dirty data. ==This is the idea and principle of CopyOnWriteArrayList. It is a copy.

  1. It is best suited for applications with the following characteristics: List sizes are usually kept small, and read-only operations are much more
    For mutable operations, conflicts between threads need to be prevented during traversal.
  2. It is thread safe.
  3. Variable operations (add(), set(), and remove() are usually required to copy the entire base array.
    Etc.) is very expensive.
  4. Iterators support immutable operations such as hasNext(), next(), but not variable remove().
  5. Iterators are fast to traverse and do not conflict with other threads. Iterations are constructed
    Iterators depend on a constant array snapshot when using the.
  6. Low efficiency of exclusive locks: using read-write separation to solve
  7. Write thread acquired lock, other write threads blocked
  8. Copy Ideas

Set

Set set = new CopyOnWriteArraySet<>();
Solve the problem

Map

HashTable

   Map<String,String> map = new Hashtable<>();
        for (int i = 0; i < 300; i++) {
            new Thread(() -> {
                map.put(UUID.randomUUID().toString().substring(8),UUID.randomUUID().toString().substring(16));
                System.out.println(map);
            }).start();
        }

It is important to mention hashtable, the underlying array + chain table implementation, whether key or value cannot be null, thread-safe. Thread-safe is achieved by locking the entire HashTable while modifying data, which is not very efficient and is not recommended.

ConcurrentHashMap

 Map<String,String> map = new ConcurrentHashMap<>();
        for (int i = 0; i < 300; i++) {
            new Thread(() -> {
                map.put(UUID.randomUUID().toString().substring(8),UUID.randomUUID().toString().substring(16));
                System.out.println(map);
            }).start();
        }

This is recommended, so why is it recommended? You must first understand the question, why is the collection thread safe?
ConcurrentHashMap is made up of one Segment array and multiple HashEntry arrays
Segment s are re-entrantLocks that play the role of locks in ConcurrentHashMap; HashEntry stores key-value pairs of data.
So why is he efficient?
That's why I said earlier that synchronous locks can't carry a lot of concurrency at all. Because HashTable s are the stream where many threads compete for a lock. ConcurrentHashMap locks are very small in size, with one lock per segment. So they are efficient.

summary

1. Thread security and incomplete thread integration
There are two types of collection types, thread-safe and thread-insecure, common examples are:
ArrayList ----- Vector
HashMap -----HashTable
However, these are all implemented by synchronized keywords, which are inefficient
2.Collections Build Thread Security Collection
3.java.util.concurrent under concurrent package
The CopyOnWriteArrayList CopyOnWriteArraySet type ensures thread security through dynamic arrays and thread security.

Forecast

The next section is the volatile keyword.

Topics: Java Scala Rust JUC