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
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
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
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
HashSet underlying structure
Similarly, the underlying structure of HashSet is HashMap
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<>();