1. Why Do We Need Concurrent Collections?
In Java, collections like ArrayList, HashMap, and HashSet are not thread-safe. When multiple threads access and modify these collections simultaneously, it may lead to race conditions, data corruption, or unpredictable behavior.
To solve this problem, Java provides two main approaches for thread-safe collections:
- Synchronized Collections (Legacy approach)
- Concurrent Collections (Modern, optimized approach)
2. The Problem with Non-Thread-Safe Collections
Example: ArrayList in a Multi-Threaded Environment
import java.util.ArrayList;
import java.util.List;
public class NonThreadSafeList {
public static void main(String[] args) {
List<String> list = new ArrayList<>();
Runnable task = () -> {
for (int i = 0; i < 1000; i++) {
list.add("Item " + i);
}
};
Thread t1 = new Thread(task);
Thread t2 = new Thread(task);
t1.start();
t2.start();
try {
t1.join();
t2.join();
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("List size: " + list.size());
}
}
Expected Output
- The list should contain 2000 elements.
Actual Output (Due to race conditions)
- The output varies, and list.size() may be less than 2000 due to lost updates caused by concurrent modifications.
3. Solution 1: Synchronized Collections (Collections.synchronized* Methods)
Java provides synchronized wrappers for collections in the Collections class.
How to Make Collections Thread-Safe Using Synchronization
import java.util.Collections;
import java.util.List;
import java.util.ArrayList;
public class SynchronizedListExample {
public static void main(String[] args) {
List<String> list = Collections.synchronizedList(new ArrayList<>());
Runnable task = () -> {
for (int i = 0; i < 1000; i++) {
list.add("Item " + i);
}
};
Thread t1 = new Thread(task);
Thread t2 = new Thread(task);
t1.start();
t2.start();
try {
t1.join();
t2.join();
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("List size: " + list.size());
}
}
✅ Advantages
- Easy to use (Collections.synchronizedList(), synchronizedMap(), etc.).
- Ensures that only one thread can modify the collection at a time.
❌ Disadvantages
- Low performance due to global synchronization (blocking).
- Manual synchronization needed for iteration:
synchronized (list) { for (String item : list) { System.out.println(item); } }
4. Solution 2: Concurrent Collections (Optimized for Multi-Threading)
Java introduced java.util.concurrent package to provide thread-safe, non-blocking collections.
Key Concurrent Collections in Java
Collection Thread-Safe Alternative
ArrayList | CopyOnWriteArrayList |
HashMap | ConcurrentHashMap |
HashSet | CopyOnWriteArraySet |
Queue | ConcurrentLinkedQueue, BlockingQueue |
(1) CopyOnWriteArrayList - Thread-Safe Alternative to ArrayList
CopyOnWriteArrayList creates a new copy of the list for every write operation, making it ideal for scenarios where reads are frequent but writes are rare.
import java.util.concurrent.CopyOnWriteArrayList;
public class CopyOnWriteArrayListExample {
public static void main(String[] args) {
CopyOnWriteArrayList<String> list = new CopyOnWriteArrayList<>();
Runnable task = () -> {
for (int i = 0; i < 1000; i++) {
list.add("Item " + i);
}
};
Thread t1 = new Thread(task);
Thread t2 = new Thread(task);
t1.start();
t2.start();
try {
t1.join();
t2.join();
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("List size: " + list.size());
}
}
✅ Advantages
- No need for explicit synchronization (thread-safe).
- Fast reads (no locking needed).
❌ Disadvantages
- Inefficient for frequent writes (creates a copy on every write).
(2) ConcurrentHashMap - Thread-Safe Alternative to HashMap
Unlike HashMap, ConcurrentHashMap divides the map into multiple segments, reducing lock contention.
import java.util.concurrent.ConcurrentHashMap;
import java.util.Map;
public class ConcurrentHashMapExample {
public static void main(String[] args) {
Map<String, Integer> map = new ConcurrentHashMap<>();
Runnable task = () -> {
for (int i = 0; i < 1000; i++) {
map.put("Key" + i, i);
}
};
Thread t1 = new Thread(task);
Thread t2 = new Thread(task);
t1.start();
t2.start();
try {
t1.join();
t2.join();
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("Map size: " + map.size());
}
}
✅ Advantages
- High performance in concurrent environments.
- Read operations do not block writes.
❌ Disadvantages
- Slightly higher memory usage than HashMap.
(3) BlockingQueue - Thread-Safe Alternative for Queues
Used for Producer-Consumer patterns, allowing threads to block when the queue is full or empty.
import java.util.concurrent.BlockingQueue;
import java.util.concurrent.ArrayBlockingQueue;
public class BlockingQueueExample {
public static void main(String[] args) {
BlockingQueue<Integer> queue = new ArrayBlockingQueue<>(5);
Thread producer = new Thread(() -> {
try {
for (int i = 1; i <= 5; i++) {
queue.put(i);
System.out.println("Produced: " + i);
}
} catch (InterruptedException e) {
e.printStackTrace();
}
});
Thread consumer = new Thread(() -> {
try {
for (int i = 1; i <= 5; i++) {
int item = queue.take();
System.out.println("Consumed: " + item);
}
} catch (InterruptedException e) {
e.printStackTrace();
}
});
producer.start();
consumer.start();
}
}
5. Synchronized Collections vs. Concurrent Collections
Feature Synchronized Collections Concurrent Collections
Locking | Global Lock | Fine-grained Locking |
Performance | Slower (blocks all threads) | Faster (non-blocking reads) |
Iteration | Requires manual synchronization | Thread-safe iteration |
Best For | Low concurrency scenarios | High-performance multi-threading |
6. Summary
✅ What We Learned
- Collections like ArrayList, HashMap are NOT thread-safe.
- Use Collections.synchronizedList() for basic synchronization, but it has performance issues.
- Use Concurrent Collections (ConcurrentHashMap, CopyOnWriteArrayList) for better performance.
- Use BlockingQueue for producer-consumer scenarios.
'JAVA' 카테고리의 다른 글
Java Runnable vs Callable – What's the Difference? (0) | 2025.03.25 |
---|---|
intro Executor (0) | 2025.03.25 |
Atomic operations and cas (0) | 2025.03.18 |
Collection: hash, hashCode and equals (0) | 2025.03.18 |
Producer-Consumer Problem (0) | 2025.03.11 |