Java Concurrency Deep Dive – Part 8: Real‑World Pitfalls and Post‑mortem
This article reviews common Java concurrency pitfalls—including deadlocks, ThreadLocal memory leaks, Integer‑cache loops, unbounded thread‑pool queues, and swallowed exceptions—illustrates each with code samples, analyzes real production incidents, and provides a concise checklist and tool‑selection guide for safe concurrent programming.
1. Deadlock Cases
Transfer deadlock
public class Account {
private int balance;
// ❌ Wrong: A transfers to B and B transfers to A simultaneously, causing deadlock
public void transfer(Account target, int amount) {
synchronized (this) {
synchronized (target) {
this.balance -= amount;
target.balance += amount;
}
}
}
// ✅ Correct: acquire locks in a fixed order
public void transferSafe(Account target, int amount) {
Account first = this.hashCode() < target.hashCode() ? this : target;
Account second = this.hashCode() < target.hashCode() ? target : this;
synchronized (first) {
synchronized (second) {
this.balance -= amount;
target.balance += amount;
}
}
}
}ThreadLocal memory leak
public class UserContext {
private static final ThreadLocal<User> currentUser = new ThreadLocal<>();
public void process() {
currentUser.set(new User("张三"));
// business logic ...
// forgot to clean up!
}
}
public void processSafe() {
try {
currentUser.set(new User("张三"));
// business logic ...
} finally {
currentUser.remove();
}
}Integer cache dead loop
// ❌ Wrong: Integer.valueOf caches -128~127, leading to identical lock objects
public class Counter {
private static final Map<Integer, Object> locks = new ConcurrentHashMap<>();
public void increment(Integer id) {
synchronized (locks.computeIfAbsent(id, k -> new Object())) {
// business logic
}
}
}
// ✅ Correct: use a dedicated Lock object
private static final Map<Integer, ReentrantLock> locks = new ConcurrentHashMap<>();
public void incrementSafe(Integer id) {
ReentrantLock lock = locks.computeIfAbsent(id, k -> new ReentrantLock());
lock.lock();
try {
// business logic
} finally {
lock.unlock();
}
}2. ThreadPool Pitfalls
Unbounded queue causing OOM
// ❌ Wrong: LinkedBlockingQueue is unbounded, tasks may pile up and OOM
ExecutorService pool = Executors.newFixedThreadPool(10);
// ✅ Correct: use a bounded queue
ThreadPoolExecutor pool = new ThreadPoolExecutor(
10, 20, 60L, TimeUnit.SECONDS,
new ArrayBlockingQueue<>(1000), // bounded queue
new ThreadPoolExecutor.CallerRunsPolicy()
);Swallowed exceptions
// ❌ Wrong: exception thrown inside task is not caught by outer try‑catch
pool.execute(() -> {
throw new RuntimeException("error");
});
// ✅ Correct: handle exception inside the task
pool.execute(() -> {
try {
// business logic
} catch (Exception e) {
log.error("Task execution exception", e);
}
});
// ✅ Also possible: capture exception via Future
Future<?> future = pool.submit(() -> {
throw new RuntimeException("error");
});
try {
future.get();
} catch (ExecutionException e) {
log.error("Task execution exception", e.getCause());
}3. Concurrency Tool Selection
Simple counter : AtomicLong – lightweight, high performance.
High‑performance counter : LongAdder – segmented counting, faster than AtomicLong.
Concurrent map : ConcurrentHashMap – high concurrency, lock‑free reads.
Concurrent list : CopyOnWriteArrayList – read‑many, write‑few.
Blocking queue : ArrayBlockingQueue – bounded, FIFO.
Delay queue : DelayQueue – delayed execution.
Read‑write lock : ReentrantReadWriteLock – read‑many, write‑few.
ConcurrentHashMap usage notes
// ❌ Wrong: composite operation is not atomic
if (!map.containsKey(key)) {
map.put(key, value);
}
// ✅ Correct: use putIfAbsent
map.putIfAbsent(key, value);
// ✅ Correct: use computeIfAbsent
map.computeIfAbsent(key, k -> expensiveCompute(k));4. Production Incident Reviews
ThreadPool misconfiguration
Symptom: peak‑time response time jumps from 50 ms to 5 s, many requests timeout.
Root cause: corePoolSize set to 10, maxPoolSize to 20, and the queue is an unbounded LinkedBlockingQueue. During peak load tasks accumulate in the queue, causing long wait times.
Fix: adjust pool size based on CPU cores and replace the unbounded queue with a bounded ArrayBlockingQueue.
int cpuCores = Runtime.getRuntime().availableProcessors();
ThreadPoolExecutor pool = new ThreadPoolExecutor(
cpuCores * 2, // IO‑bound, core threads = CPU*2
cpuCores * 4, // max threads = CPU*4
60L, TimeUnit.SECONDS,
new ArrayBlockingQueue<>(2000), // bounded queue
new ThreadPoolExecutor.CallerRunsPolicy()
);Concurrent modification of a collection
Symptom: occasional ConcurrentModificationException.
Root cause: another thread modifies the collection while it is being iterated.
Fixes:
Use iterator's remove() method.
Replace the collection with a concurrent one such as CopyOnWriteArrayList.
Collect items to delete in a temporary list and remove them in batch.
// ❌ Wrong
List<String> list = new ArrayList<>();
for (String s : list) {
if (s.equals("test")) {
list.remove(s); // may throw ConcurrentModificationException
}
}
// ✅ Correct method 1: iterator.remove()
Iterator<String> it = list.iterator();
while (it.hasNext()) {
if (it.next().equals("test")) {
it.remove();
}
}
// ✅ Correct method 2: use concurrent collection
CopyOnWriteArrayList<String> list = new CopyOnWriteArrayList<>();
// ✅ Correct method 3: batch removal
List<String> toRemove = new ArrayList<>();
for (String s : list) {
if (s.equals("test")) {
toRemove.add(s);
}
}
list.removeAll(toRemove);5. Concurrency Best‑Practice Checklist
Use thread pools – avoid creating threads manually.
Use bounded queues – prevent OOM.
Handle exceptions properly – catch and log inside the task.
Clean up ThreadLocal – call remove() in a finally block.
Avoid composite operations – use atomic methods or protect with locks.
Choose appropriate concurrent tools – avoid synchronized for high‑performance counters.
Lock in a fixed order – prevent deadlocks.
Leverage java.util.concurrent – reuse existing utilities.
Signed-in readers can open the original source through BestHub's protected redirect.
This article has been distilled and summarized from source material, then republished for learning and reference. If you believe it infringes your rights, please contactand we will review it promptly.
Coder Trainee
Experienced in Java and Python, we share and learn together. For submissions or collaborations, DM us.
How this landed with the community
Was this worth your time?
0 Comments
Thoughtful readers leave field notes, pushback, and hard-won operational detail here.
