List collection thread unsafe operation
Collection thread unsafe case
package com.atguigu.test; 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 * @param args */ 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(); } } }
Abnormal content
java.util.ConcurrentModificationException
Question: why do concurrent modification exceptions occur?
View the source code of the add method of ArrayList
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?
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.
List collection thread safety case 1
NotSafeDemo code modification
package com.atguigu.test; 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 * @param args */ 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(); } } }
There is no concurrency exception in the running. Why?
View the add method of Vector
public synchronized boolean add(E e) { modCount++; ensureCapacityHelper(elementCount + 1); elementData[elementCount++] = e; return true; }
The add method is synchronized, thread safe!
Therefore, there are no concurrent exceptions
Collections
Collections provides the method synchronizedList
Ensure that the list is thread safe for synchronization
List collection thread safety case 2
NotSafeDemo code modification
package com.atguigu.test; import java.util.*; /** * Collection thread safety case */ public class NotSafeDemo { /** * Multiple threads modify the collection at the same time * @param args */ 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(); } } }
No concurrent modification exception
View method source code
public static <T> List<T> synchronizedList(List<T> list) { return ( list instanceof RandomAccess ? new SynchronizedRandomAccessList<>(list) : new SynchronizedList<>(list)); }
Copyonwritearraylist (key)
CopyOnWriteArrayList is equivalent to thread safe ArrayList.
Like ArrayList, it is a variable array;
However, unlike ArrayList, it has the following features:
- It is best suited for applications that:
The List size is usually kept small, and read-only operations are much more than variable operations. It is necessary to prevent conflicts between threads during traversal. - It is thread safe.
- Because you usually need to copy the entire underlying array, variable operations (add(), set(), remove(), and so on) are expensive.
- Iterators support immutable operations such as hasNext(), next(), but do not support immutable operations such as remove().
- Traversal using iterators is fast and does not conflict with other threads. When constructing iterators, iterators rely on invariant array snapshots.
Idea and principle of CopyOnWriteArrayList
- Low efficiency of exclusive lock: it is solved by the idea of separation of read and write
- The write thread obtains the lock, and other write threads are blocked
- Copy ideas:
- When we add elements to a container, we do not directly add them to the current container, but Copy the current container first.
- Copy a new container, and then add elements to the new container. After adding elements, 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 back to memory, other threads will read dirty data.
This is the idea and principle of CopyOnWriteArrayList. Just a copy.
List collection thread safety case 3
NotSafeDemo code modification
package com.atguigu.test; import java.util.*; import java.util.concurrent.CopyOnWriteArrayList; /** * Collection thread safety case */ public class NotSafeDemo { /** * Multiple threads modify the collection at the same time * @ param args */ 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(); } } }
No thread safety 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 a new array when "adding / modifying / deleting" data, it involves the operation of modifying data,
- CopyOnWriteArrayList is inefficient; 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.
Set case DEMO
package com.java.juc.lock; import java.util.*; import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.CopyOnWriteArrayList; import java.util.concurrent.CopyOnWriteArraySet; public class ThreadDemo4 { public static void main(String[] args) { //list collection thread is unsafe //listDemo(); //HashSet collection thread unsafe demo //setDemo(); /** * ConcurrentModificationException Concurrent modification exception */ //Map<String,String > map = new HashMap<>(); Map<String,String > map = new ConcurrentHashMap<>(); for(int i=0;i<30;i++){ String key = String.valueOf(i); new Thread(()->{ //Add content to collection map.put(key,UUID.randomUUID().toString().substring(0,8)); //Get content from collection System.out.println(map); },"Thread:" + key).start(); } } /** * HashSet Collection thread unsafe */ private static void setDemo(){ /** * ConcurrentModificationException Concurrent modification exception */ //Set<String> set = new HashSet<>(); Set<String> set = new CopyOnWriteArraySet<>(); for(int i=0;i<30;i++){ new Thread(()->{ //Add content to collection set.add(UUID.randomUUID().toString().substring(0,8)); //Get content from collection System.out.println(set); },"Thread:"+ String.valueOf(i)).start(); } } /** * list Collection thread unsafe */ private static void listDemo(){ /** * ConcurrentModificationException Concurrent modification exception */ // List<String> list = new ArrayList(); /** * Solution 1: Vector */ // List<String> list = new Vector<>(); /** * Solution 2: Collections.synchronizedList */ //List<String> list = Collections.synchronizedList(new ArrayList<>()); /** * Solution 3: CopyOnWriteArrayList */ List<String> list = new CopyOnWriteArrayList<>(); for(int i=0;i<10;i++){ new Thread(()->{ //Add content to collection list.add(UUID.randomUUID().toString().substring(0,8)); //Get content from collection System.out.println(list); },"Thread:"+ String.valueOf(i)).start(); } } }
Summary (key points)
1. Thread safe and thread unsafe collection
There are two types of collection types: thread safe and thread unsafe,
Common examples:
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
CopyOnWriteArrayList CopyOnWriteArraySet type ensures thread safety through dynamic array and thread safety