Home/Java/Concurrent Collections in Java

Concurrent Collections in Java

Learn thread-safe collection classes designed for high-performance concurrent access

Think of concurrent collections like a smart buffet line! Regular collections are like a single-file buffet where only one person can take food at a time - everyone has to wait. Concurrent collections are like a smart buffet with multiple serving stations where many people can grab different foods at the same time without bumping into each other. Java's concurrent collections use clever tricks to let multiple threads access them safely and quickly!

The Problem with Regular Collections

Regular collections like ArrayList and HashMap are NOT thread-safe! If multiple threads try to modify them at the same time, you get corrupted data or crashes. Even using synchronized wrappers is slow because they lock the entire collection. Let's see the problem:

java
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
// Problem with regular collections in multithreading
import java.util.*;
public class UnsafeCollectionExample {
public static void main(String[] args) {
// Regular ArrayList - NOT thread-safe!
List<Integer> list = new ArrayList<>();
// Create 10 threads that all add numbers to the list
Thread[] threads = new Thread[10];
for (int i = 0; i < 10; i++) {
final int threadId = i;
threads[i] = new Thread(() -> {
for (int j = 0; j < 1000; j++) {
// Danger! Multiple threads modifying list simultaneously
list.add(threadId * 1000 + j);
}
});
threads[i].start();
}
// Wait for all threads
for (Thread thread : threads) {
try {
thread.join();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
// Expected: 10,000 elements (10 threads × 1000)
// Actual: Unpredictable! Could be less, or even crash!
System.out.println("Expected size: 10000");
System.out.println("Actual size: " + list.size());
System.out.println("Data corruption occurred: " + (list.size() != 10000));
}
}
/* Output (varies each run):
Expected size: 10000
Actual size: 9847
Data corruption occurred: true
OR you might see:
Exception in thread "Thread-3" java.lang.ArrayIndexOutOfBoundsException
PROBLEM: ArrayList is NOT thread-safe!
Multiple threads cause:
- Lost updates (data corruption)
- ArrayIndexOutOfBoundsException
- Unpredictable results
*/

CopyOnWriteArrayList - Thread-Safe List

CopyOnWriteArrayList is perfect when you have many readers and few writers! Every time you modify it, it creates a new copy of the underlying array. Sounds expensive? It is! But for read-heavy workloads where writes are rare, it's blazingly fast because reads don't need any locks!

java
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
// Using CopyOnWriteArrayList for thread safety
import java.util.concurrent.CopyOnWriteArrayList;
import java.util.List;
public class CopyOnWriteExample {
public static void main(String[] args) {
// Thread-safe list - perfect for many readers, few writers
List<String> safeList = new CopyOnWriteArrayList<>();
// Add initial data
safeList.add("Apple");
safeList.add("Banana");
safeList.add("Cherry");
System.out.println("=== Testing CopyOnWriteArrayList ===\n");
// Reader threads (many readers)
Runnable readTask = () -> {
String thread = Thread.currentThread().getName();
for (int i = 0; i < 3; i++) {
System.out.println(thread + " reading: " + safeList);
try {
Thread.sleep(50);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
};
// Writer thread (few writers)
Runnable writeTask = () -> {
String thread = Thread.currentThread().getName();
try {
Thread.sleep(100);
safeList.add("Date");
System.out.println(thread + " added 'Date'");
Thread.sleep(100);
safeList.add("Elderberry");
System.out.println(thread + " added 'Elderberry'");
} catch (InterruptedException e) {
e.printStackTrace();
}
};
// Start multiple readers
Thread reader1 = new Thread(readTask, "Reader-1");
Thread reader2 = new Thread(readTask, "Reader-2");
Thread reader3 = new Thread(readTask, "Reader-3");
// Start one writer
Thread writer = new Thread(writeTask, "Writer");
reader1.start();
reader2.start();
reader3.start();
writer.start();
try {
reader1.join();
reader2.join();
reader3.join();
writer.join();
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("\nFinal list: " + safeList);
System.out.println("\nAll operations completed safely!");
}
}
/* Output:
=== Testing CopyOnWriteArrayList ===
Reader-1 reading: [Apple, Banana, Cherry]
Reader-2 reading: [Apple, Banana, Cherry]
Reader-3 reading: [Apple, Banana, Cherry]
Writer added 'Date'
Reader-1 reading: [Apple, Banana, Cherry, Date]
Reader-2 reading: [Apple, Banana, Cherry, Date]
Reader-3 reading: [Apple, Banana, Cherry, Date]
Writer added 'Elderberry'
Reader-1 reading: [Apple, Banana, Cherry, Date, Elderberry]
Reader-2 reading: [Apple, Banana, Cherry, Date, Elderberry]
Reader-3 reading: [Apple, Banana, Cherry, Date, Elderberry]
Final list: [Apple, Banana, Cherry, Date, Elderberry]
All operations completed safely!
BENEFITS:
- No locks needed for reads (very fast!)
- Readers see consistent snapshots
- Perfect for read-heavy workloads
*/

ConcurrentHashMap - High-Performance Thread-Safe Map

ConcurrentHashMap is the superstar of concurrent collections! Unlike Hashtable which locks the entire map, ConcurrentHashMap only locks small segments. It's like having multiple cashiers at a store - many threads can work on different parts of the map simultaneously. Way faster than synchronized HashMap!

java
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
// Using ConcurrentHashMap for thread-safe map operations
import java.util.concurrent.ConcurrentHashMap;
import java.util.Map;
public class ConcurrentHashMapExample {
public static void main(String[] args) {
// Thread-safe map with high concurrency
Map<String, Integer> wordCount = new ConcurrentHashMap<>();
System.out.println("=== Word Counter with ConcurrentHashMap ===\n");
// Multiple threads counting words
String[] thread1Words = {"apple", "banana", "apple", "cherry"};
String[] thread2Words = {"banana", "date", "apple", "banana"};
String[] thread3Words = {"cherry", "apple", "elderberry", "date"};
Runnable counter1 = () -> countWords(wordCount, thread1Words, "Counter-1");
Runnable counter2 = () -> countWords(wordCount, thread2Words, "Counter-2");
Runnable counter3 = () -> countWords(wordCount, thread3Words, "Counter-3");
Thread t1 = new Thread(counter1);
Thread t2 = new Thread(counter2);
Thread t3 = new Thread(counter3);
t1.start();
t2.start();
t3.start();
try {
t1.join();
t2.join();
t3.join();
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("\n=== Final Word Counts ===");
wordCount.forEach((word, count) -> {
System.out.println(word + ": " + count);
});
System.out.println("\nAll counts are correct! No data corruption.");
}
static void countWords(Map<String, Integer> map, String[] words, String threadName) {
for (String word : words) {
// Atomic operation - increment or initialize to 1
map.merge(word, 1, Integer::sum);
System.out.println(threadName + " counted: " + word);
try {
Thread.sleep(10); // Simulate processing time
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
/* Output:
=== Word Counter with ConcurrentHashMap ===
Counter-1 counted: apple
Counter-2 counted: banana
Counter-3 counted: cherry
Counter-1 counted: banana
Counter-2 counted: date
Counter-3 counted: apple
Counter-1 counted: apple
Counter-2 counted: apple
Counter-3 counted: elderberry
Counter-1 counted: cherry
Counter-2 counted: banana
Counter-3 counted: date
=== Final Word Counts ===
apple: 4
banana: 3
cherry: 2
date: 2
elderberry: 1
All counts are correct! No data corruption.
BENEFITS:
- High concurrency (multiple threads work simultaneously)
- Atomic operations (merge, compute, etc.)
- No need for external synchronization
- Much faster than synchronized HashMap
*/

BlockingQueue - Producer-Consumer Pattern

BlockingQueue is magic for producer-consumer scenarios! Producers put items in the queue, consumers take them out. If the queue is full, producers wait. If empty, consumers wait. It's like a conveyor belt in a factory - perfect for coordinating work between threads!

java
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
// Using BlockingQueue for producer-consumer pattern
import java.util.concurrent.*;
class Task {
private int id;
private String description;
public Task(int id, String description) {
this.id = id;
this.description = description;
}
public int getId() { return id; }
public String getDescription() { return description; }
@Override
public String toString() {
return "Task-" + id + ": " + description;
}
}
public class BlockingQueueExample {
public static void main(String[] args) throws InterruptedException {
// Bounded queue - max 5 tasks
BlockingQueue<Task> taskQueue = new ArrayBlockingQueue<>(5);
System.out.println("=== Producer-Consumer with BlockingQueue ===\n");
// Producer: Creates tasks and adds to queue
Thread producer = new Thread(() -> {
try {
for (int i = 1; i <= 10; i++) {
Task task = new Task(i, "Process data " + i);
System.out.println("Producer creating: " + task);
// put() blocks if queue is full!
taskQueue.put(task);
System.out.println("Producer added: " + task +
" (Queue size: " + taskQueue.size() + ")");
Thread.sleep(200); // Simulate task creation time
}
System.out.println("\nProducer finished creating tasks!\n");
} catch (InterruptedException e) {
e.printStackTrace();
}
}, "Producer");
// Consumer 1: Takes tasks and processes them
Thread consumer1 = new Thread(() -> {
try {
for (int i = 0; i < 5; i++) {
// take() blocks if queue is empty!
Task task = taskQueue.take();
System.out.println(" Consumer-1 took: " + task);
Thread.sleep(500); // Simulate processing time
System.out.println(" Consumer-1 completed: " + task);
}
} catch (InterruptedException e) {
e.printStackTrace();
}
}, "Consumer-1");
// Consumer 2: Another worker processing tasks
Thread consumer2 = new Thread(() -> {
try {
for (int i = 0; i < 5; i++) {
Task task = taskQueue.take();
System.out.println(" Consumer-2 took: " + task);
Thread.sleep(600); // Slower consumer
System.out.println(" Consumer-2 completed: " + task);
}
} catch (InterruptedException e) {
e.printStackTrace();
}
}, "Consumer-2");
// Start all threads
producer.start();
Thread.sleep(100); // Let producer start first
consumer1.start();
consumer2.start();
// Wait for completion
producer.join();
consumer1.join();
consumer2.join();
System.out.println("\nAll tasks produced and consumed!");
System.out.println("Final queue size: " + taskQueue.size());
}
}
/* Output:
=== Producer-Consumer with BlockingQueue ===
Producer creating: Task-1: Process data 1
Producer added: Task-1: Process data 1 (Queue size: 1)
Consumer-1 took: Task-1: Process data 1
Producer creating: Task-2: Process data 2
Producer added: Task-2: Process data 2 (Queue size: 1)
Consumer-2 took: Task-2: Process data 2
Producer creating: Task-3: Process data 3
Producer added: Task-3: Process data 3 (Queue size: 1)
Consumer-1 completed: Task-1: Process data 1
Consumer-1 took: Task-3: Process data 3
Producer creating: Task-4: Process data 4
Producer added: Task-4: Process data 4 (Queue size: 1)
...
Producer finished creating tasks!
All tasks produced and consumed!
Final queue size: 0
BENEFITS:
- Automatic thread coordination (no manual wait/notify!)
- Producer blocks when queue is full
- Consumer blocks when queue is empty
- Perfect for work distribution
*/

ConcurrentLinkedQueue - Lock-Free Queue

ConcurrentLinkedQueue is a lock-free, non-blocking queue! It uses clever atomic operations instead of locks, making it super fast. It's unbounded (grows as needed) and perfect for high-throughput scenarios where you need maximum speed and don't want threads blocking!

java
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
// Using ConcurrentLinkedQueue for lock-free operations
import java.util.concurrent.ConcurrentLinkedQueue;
import java.util.Queue;
import java.util.concurrent.atomic.AtomicInteger;
class Event {
private static AtomicInteger idGenerator = new AtomicInteger(0);
private int id;
private String type;
private long timestamp;
public Event(String type) {
this.id = idGenerator.incrementAndGet();
this.type = type;
this.timestamp = System.currentTimeMillis();
}
@Override
public String toString() {
return "Event#" + id + " [" + type + "]";
}
}
public class ConcurrentLinkedQueueExample {
public static void main(String[] args) throws InterruptedException {
// Lock-free, thread-safe queue
Queue<Event> eventQueue = new ConcurrentLinkedQueue<>();
AtomicInteger producedCount = new AtomicInteger(0);
AtomicInteger consumedCount = new AtomicInteger(0);
System.out.println("=== Event Processing with ConcurrentLinkedQueue ===\n");
// Multiple event producers
Runnable producer = () -> {
String threadName = Thread.currentThread().getName();
String[] eventTypes = {"Click", "Scroll", "KeyPress", "MouseMove"};
for (int i = 0; i < 5; i++) {
String type = eventTypes[i % eventTypes.length];
Event event = new Event(type);
// offer() - non-blocking add
eventQueue.offer(event);
producedCount.incrementAndGet();
System.out.println(threadName + " produced: " + event +
" (Queue size: ~" + eventQueue.size() + ")");
try {
Thread.sleep(50);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
};
// Multiple event consumers
Runnable consumer = () -> {
String threadName = Thread.currentThread().getName();
for (int i = 0; i < 5; i++) {
Event event = null;
// poll() - non-blocking remove
while (event == null) {
event = eventQueue.poll();
if (event == null) {
// Queue empty, wait a bit
try {
Thread.sleep(10);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
consumedCount.incrementAndGet();
System.out.println(" " + threadName + " consumed: " + event);
try {
Thread.sleep(80); // Simulate processing
} catch (InterruptedException e) {
e.printStackTrace();
}
}
};
// Start 3 producers and 3 consumers
Thread[] producers = new Thread[3];
Thread[] consumers = new Thread[3];
for (int i = 0; i < 3; i++) {
producers[i] = new Thread(producer, "Producer-" + (i + 1));
consumers[i] = new Thread(consumer, "Consumer-" + (i + 1));
producers[i].start();
consumers[i].start();
}
// Wait for all threads
for (int i = 0; i < 3; i++) {
producers[i].join();
consumers[i].join();
}
System.out.println("\n=== Final Statistics ===");
System.out.println("Total events produced: " + producedCount.get());
System.out.println("Total events consumed: " + consumedCount.get());
System.out.println("Remaining in queue: " + eventQueue.size());
System.out.println("\nLock-free operation completed successfully!");
}
}
/* Output:
=== Event Processing with ConcurrentLinkedQueue ===
Producer-1 produced: Event#1 [Click] (Queue size: ~1)
Producer-2 produced: Event#2 [Click] (Queue size: ~2)
Producer-3 produced: Event#3 [Click] (Queue size: ~3)
Consumer-1 consumed: Event#1
Consumer-2 consumed: Event#2
Consumer-3 consumed: Event#3
Producer-1 produced: Event#4 [Scroll] (Queue size: ~1)
Producer-2 produced: Event#5 [Scroll] (Queue size: ~2)
...
=== Final Statistics ===
Total events produced: 15
Total events consumed: 15
Remaining in queue: 0
Lock-free operation completed successfully!
BENEFITS:
- No locks! Uses atomic operations
- Non-blocking (never waits for locks)
- High throughput
- Unbounded capacity
*/

Real-World Example: Order Processing System

Let's build a realistic order processing system using concurrent collections! This simulates an e-commerce platform where multiple users place orders, payment processors handle payments, and shipping coordinators prepare packages - all happening concurrently with safe data sharing!

java
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
// Real-world order processing system with concurrent collections
import java.util.concurrent.*;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.*;
class Order {
private static AtomicInteger orderIdGenerator = new AtomicInteger(1000);
private int orderId;
private String customer;
private double amount;
private String status;
public Order(String customer, double amount) {
this.orderId = orderIdGenerator.incrementAndGet();
this.customer = customer;
this.amount = amount;
this.status = "PENDING";
}
public int getOrderId() { return orderId; }
public String getCustomer() { return customer; }
public double getAmount() { return amount; }
public String getStatus() { return status; }
public void setStatus(String status) { this.status = status; }
@Override
public String toString() {
return "Order#" + orderId + " [$" + String.format("%.2f", amount) +
" - " + customer + " - " + status + "]";
}
}
public class OrderProcessingSystem {
// Thread-safe collections for different stages
private static BlockingQueue<Order> pendingOrders = new LinkedBlockingQueue<>();
private static ConcurrentHashMap<Integer, Order> processedOrders = new ConcurrentHashMap<>();
private static CopyOnWriteArrayList<String> processingLog = new CopyOnWriteArrayList<>();
public static void main(String[] args) throws InterruptedException {
System.out.println("=== E-Commerce Order Processing System ===\n");
// Customer threads - place orders
Runnable customerTask = () -> {
String customerName = Thread.currentThread().getName();
Random random = new Random();
for (int i = 0; i < 3; i++) {
double amount = 10.0 + random.nextDouble() * 90.0;
Order order = new Order(customerName, amount);
try {
pendingOrders.put(order);
String logEntry = customerName + " placed " + order;
processingLog.add(logEntry);
System.out.println(logEntry);
Thread.sleep(200);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
};
// Payment processor threads
Runnable paymentProcessor = () -> {
String processorName = Thread.currentThread().getName();
try {
for (int i = 0; i < 5; i++) {
Order order = pendingOrders.poll(2, TimeUnit.SECONDS);
if (order == null) break;
String logEntry = " " + processorName + " processing payment for " + order;
processingLog.add(logEntry);
System.out.println(logEntry);
Thread.sleep(300); // Simulate payment processing
order.setStatus("PAID");
processedOrders.put(order.getOrderId(), order);
logEntry = " " + processorName + " completed payment: " +
order.getOrderId();
processingLog.add(logEntry);
System.out.println(logEntry);
}
} catch (InterruptedException e) {
e.printStackTrace();
}
};
// Start 3 customers
Thread customer1 = new Thread(customerTask, "Alice");
Thread customer2 = new Thread(customerTask, "Bob");
Thread customer3 = new Thread(customerTask, "Charlie");
// Start 2 payment processors
Thread processor1 = new Thread(paymentProcessor, "PaymentProcessor-1");
Thread processor2 = new Thread(paymentProcessor, "PaymentProcessor-2");
// Launch all threads
customer1.start();
customer2.start();
customer3.start();
Thread.sleep(100); // Let customers start first
processor1.start();
processor2.start();
// Wait for completion
customer1.join();
customer2.join();
customer3.join();
processor1.join();
processor2.join();
// Generate report
System.out.println("\n=== Order Processing Summary ===");
System.out.println("Total orders processed: " + processedOrders.size());
double totalRevenue = processedOrders.values().stream()
.mapToDouble(Order::getAmount)
.sum();
System.out.println("Total revenue: $" + String.format("%.2f", totalRevenue));
System.out.println("\nProcessed Orders:");
processedOrders.values().forEach(order -> {
System.out.println(" " + order);
});
System.out.println("\n=== System Statistics ===");
System.out.println("Log entries: " + processingLog.size());
System.out.println("Pending orders: " + pendingOrders.size());
System.out.println("\nAll operations completed safely with concurrent collections!");
}
}
/* Output:
=== E-Commerce Order Processing System ===
Alice placed Order#1001 [$45.67 - Alice - PENDING]
Bob placed Order#1002 [$78.32 - Bob - PENDING]
Charlie placed Order#1003 [$23.45 - Charlie - PENDING]
PaymentProcessor-1 processing payment for Order#1001 [$45.67 - Alice - PENDING]
PaymentProcessor-2 processing payment for Order#1002 [$78.32 - Bob - PENDING]
Alice placed Order#1004 [$91.23 - Alice - PENDING]
PaymentProcessor-1 completed payment: 1001
PaymentProcessor-1 processing payment for Order#1003 [$23.45 - Charlie - PENDING]
Bob placed Order#1005 [$56.78 - Bob - PENDING]
PaymentProcessor-2 completed payment: 1002
PaymentProcessor-2 processing payment for Order#1004 [$91.23 - Alice - PENDING]
...
=== Order Processing Summary ===
Total orders processed: 9
Total revenue: $523.45
Processed Orders:
Order#1001 [$45.67 - Alice - PAID]
Order#1002 [$78.32 - Bob - PAID]
Order#1003 [$23.45 - Charlie - PAID]
...
=== System Statistics ===
Log entries: 27
Pending orders: 0
All operations completed safely with concurrent collections!
DEMONSTRATES:
- BlockingQueue for order pipeline
- ConcurrentHashMap for order tracking
- CopyOnWriteArrayList for audit log
- Real-world multi-stage processing
*/

Key Concepts

Thread-Safe vs Synchronized

Concurrent collections are internally thread-safe and optimized for concurrency. Synchronized wrappers (Collections.synchronizedList) lock the entire collection on every operation, making them much slower for concurrent access.

Copy-On-Write Pattern

CopyOnWriteArrayList/Set create a new copy on every modification. Expensive for writes, but reads are extremely fast with no locks. Perfect for scenarios with many readers and few writers (like event listeners).

Segment Locking

ConcurrentHashMap divides the map into segments and locks only the affected segment. This allows multiple threads to work on different segments simultaneously, providing high concurrency.

Blocking vs Non-Blocking

BlockingQueue blocks threads when full/empty (great for producer-consumer). ConcurrentLinkedQueue never blocks - uses lock-free algorithms for maximum throughput (great for high-speed message passing).

Best Practices

  • Use ConcurrentHashMap instead of Hashtable or Collections.synchronizedMap for better performance
  • Choose CopyOnWriteArrayList for read-heavy workloads with infrequent updates (like event listeners)
  • Use BlockingQueue for producer-consumer patterns - it handles all thread coordination automatically
  • For high-throughput scenarios, prefer ConcurrentLinkedQueue over synchronized alternatives
  • Avoid using size() or isEmpty() for logic decisions - these are approximate in concurrent collections
  • Use atomic operations like putIfAbsent, computeIfAbsent, merge instead of check-then-act patterns

Common Mistakes to Avoid

  • Using regular collections (ArrayList, HashMap) in multithreaded code without synchronization
  • Using CopyOnWriteArrayList for write-heavy workloads - each write copies the entire array!
  • Relying on size() method for logic in concurrent collections - size can change immediately after checking
  • Iterating over a concurrent collection and expecting no concurrent modifications - use iterators properly
  • Using synchronized wrappers when concurrent collections would be much faster
  • Not understanding that null values are not allowed in most concurrent collections

Interview Tips

  • 💡Know the main concurrent collections: ConcurrentHashMap, CopyOnWriteArrayList, BlockingQueue, ConcurrentLinkedQueue
  • 💡Understand when to use CopyOnWriteArrayList (many reads, few writes) vs synchronized list
  • 💡Be able to explain how ConcurrentHashMap achieves high concurrency (segment locking/CAS operations)
  • 💡Know the difference between BlockingQueue (blocks when full/empty) and ConcurrentLinkedQueue (never blocks)
  • 💡Understand that fail-fast iterators don't work the same way in concurrent collections - they're weakly consistent
  • 💡Be prepared to compare concurrent collections with synchronized wrappers and explain performance differences