In-depth analysis of thread safety issues of commonly used Java collections and mappings
1. Basic knowledge of thread safety
In a concurrent programming environment, when multiple threads operate the same collection object at the same time, if synchronization measures are not taken, the following typical problems may be caused:
- Data competition: Multiple threads modify data simultaneously, resulting in unpredictable results
- Inconsistent status: Some threads see the intermediate state of the collection
- Memory visibility: Thread local cache is not synchronized with main memory data
- The risk of a vicious cycle: Specific operations trigger infinite loops (such as JDK7's HashMap expansion)
2. Analysis of typical non-thread-safe set problems
1. ArrayList's concurrent trap
// Error exampleList<Integer> list = new ArrayList<>(); ExecutorService pool = (10); for (int i = 0; i < 1000; i++) { (() -> (new Random().nextInt())); } // The run result may include:Elements are missing、sizeValue exception、Array out of bounds, etc.
The root of the problem:
-
add()
Method non-atomic operation:elementData[size++] = e
- Multithreading simultaneously triggers expansion, resulting in array copy overwrite
- Size variable visibility problem
2. HashMap concurrent disaster
Map<String, Integer> map = new HashMap<>(); // Concurrent execution of put operations may cause:// 1. JDK7 and previous versions: The ring-linked list results in 100% CPU// 2. JDK8+ version: data loss or size count error// 3. When iteratingConcurrentModificationException
The underlying mechanism:
- The hash bucket structure causes a link list to break when expanding capacity
- Differences between head insertion method (JDK7) and tail insertion method (JDK8)
- Entry array operation without synchronization mechanism
3. HashSet's Hidden Risks
Set<Integer> set = new HashSet<>(); // In essence, it is a packaging class of HashMap. All thread safety issues are consistent with HashMap// add()Element loss may occur when concurrently called
3. Comparison of thread safety solutions
1. Synchronous packaging solution
// Use Collections tool classList<String> syncList = (new ArrayList<>()); Map<String, Object> syncMap = (new HashMap<>()); // Features:// 1. All methods use synchronized sync blocks// 2. Iterators need to be manually synchronized// 3. Large lock size,Poor performance
2. Traditional thread-safe collection
// Vector/Hashtable solutionVector<String> vector = new Vector<>(); Hashtable<String, Integer> table = new Hashtable<>(); // shortcoming:// 1. Full table lock results in low throughput// 2. It has been gradually replaced by concurrent containers
3. Modern concurrent container (package)
3.1 CopyOnWriteArrayList
List<String> cowList = new CopyOnWriteArrayList<>(); // Implementation principle:// 1. Copy new array during write operation// 2. Final consistency guarantee// Applicable scenarios:Read more and write less(Whitelist configuration)
3.2 ConcurrentHashMap
Map<String, Object> concurrentMap = new ConcurrentHashMap<>(); // JDK8+ implementation features:// 1. Upgrade the segment lock to CAS+synchronized// 2. Node lock granularity (locking a single hash bucket)// 3. Support concurrency settings
3.3 ConcurrentSkipListMap
NavigableMap<String, Integer> skipMap = new ConcurrentSkipListMap<>(); // Features:// 1. Ordered Map based on table jump implementation// 2. Lockless reading,Write usageCAS
4. Analysis of the principle of concurrent container implementation
1. CopyOnWriteArrayList copy-on-write mechanism
public boolean add(E e) { final ReentrantLock lock = ; (); try { Object[] elements = getArray(); int len = ; Object[] newElements = (elements, len + 1); newElements[len] = e; setArray(newElements); return true; } finally { (); } }
2. ConcurrentHashMap concurrency control
Key implementation of JDK8:
- Hash bucket array + linked list/red and black tree
- CAS operation realizes lock-free reading
- Synchronized locks a single node
- Size calculation adopts LongAdder mechanism
3. Concurrent queue implementation comparison
Queue Type | Locking mechanism | Applicable scenarios |
---|---|---|
ConcurrentLinkedQueue | CAS lock-free | High concurrent producer consumer model |
LinkedBlockingQueue | ReentrantLock Double Lock | Bounded blocking queue |
ArrayBlockingQueue | Single ReentrantLock | Fixed capacity queue |
5. Best practices and precautions
1. Selection and Decision Making Guide
- Read more and write less: CopyOnWrite series
- High concurrent write:ConcurrentHashMap
- Strong consistency requirements: Synchronous packaging class + manual lock
- Orderly requirements:ConcurrentSkipListMap
2. Avoid common misunderstandings
-
Misunderstanding:think
More secure than concurrent containers
- Iterator problem: Iterators of synchronous collections are not locked
- Compound operation vulnerability: Even if thread-safe collection is used, multiple operations still need to be synchronized
// Error example: Synchronization is still required even if using ConcurrentHashMapif (!(key)) { (key, value); // Non-atomic operation} // Correct writing:(key, value);
3. Performance optimization suggestions
- Estimated initial capacity reduction and expansion of ConcurrentHashMap
- Avoid using super large arrays in CopyOnWriteArrayList
- Set the concurrency level reasonably (ConcurrentHashMap constructor)
- Use batch operation methods (such as putAll)
6. Advanced topic expansion
1. Weak consistency iterator
- The iterator of ConcurrentHashMap reflects the status at creation
- There is no guarantee that data changes can be visible during the iteration process
2. Atomic composite operation
// Use merge method to implement atom countingConcurrentHashMap<String, Long> counterMap = new ConcurrentHashMap<>(); ("key", 1L, Long::sum);
3. Evolution of segmented locks
- JDK7's Segment segment lock (default 16 segments)
- JDK8's Node Granular Lock (locking a single hash bucket)
Summary and suggestions
- Strictly distinguish scenes: Select containers according to read and write ratio and consistency requirements
- Understand the implementation principle: Avoid misuse of concurrent container features
- Combined lock mechanism: Used with ReentrantLock if necessary
- Monitoring tool assistance: Use JConsole to observe container contention
Developers should establish the following awareness:
- There are no absolutely thread-safe containers, only relatively safe operation methods
- Concurrency problems are often exposed in high-pressure scenarios
- Full testing is a necessary means to verify thread safety
By rationally selecting concurrent containers and following best practices, the risk of collection operation in multi-threaded environments can be significantly reduced and a high-performance and reliable Java application system can be built.
This is the end of this article about thread safety of commonly used Java collections and mappings. For more related Java collections and mapping content, please search for my previous articles or continue browsing the related articles below. I hope everyone will support me in the future!