Master Java Concurrency: HashMap, ConcurrentHashMap, JMM, Thread Pools & More
Explore deep Java concurrency concepts, from HashMap internals and its JDK7/JDK8 differences to ConcurrentHashMap’s lock strategies, thread states, memory models, volatile, CAS, synchronized, thread pools, AQS, and common interview questions, providing comprehensive insights for mastering multithreaded programming.
1. HashMap
HashMap is a frequently asked interview question that tests a Java developer's fundamentals. Its key characteristics are:
HashMap does not guarantee order of entries. Both keys and values may be null. It is not thread‑safe; use Hashtable in multithreaded scenarios. In JDK8 the underlying structure is array + linked list + red‑black tree; in JDK7 it is array + linked list. The initial capacity and load factor are critical for performance and should rarely be changed. HashMap is lazily created – the internal table is built only when the first put occurs. When a bucket’s linked list is converted to a red‑black tree it first becomes a doubly linked list, then a tree; the tree and the list coexist. During put, two keys are compared to decide left or right placement in the tree. When deleting, the implementation checks whether the red‑black tree needs to be converted back to a list. The root node and the table entry are merged via the MoveRootToFront operation.
Common interview points for HashMap include its internal data structure, put/get/remove processes, hash function, resizing, important parameters, thread‑safety, differences between JDK7 and JDK8, and the linked‑list‑to‑tree conversion.
2. ConcurrentHashMap
ConcurrentHashMap is a concurrent container widely used in multithreaded environments. Its implementation differs significantly between JDK7 and JDK8.
2.1 JDK7
In JDK7 ConcurrentHashMap uses a segment + HashEntry design with segment‑level locks. The number of segments determines the maximum concurrency; the segment array cannot be resized, only the HashEntry objects can grow.
The put process roughly follows the diagram below:
ConcurrentHashMap allows multiple modifications to proceed concurrently by using lock‑splitting. Each segment is essentially a small HashTable protected by a ReentrantLock.
2.2 JDK8
In JDK8 the segment lock is removed. Concurrency is achieved with CAS + synchronized, and the bucket entries are now Node objects that can form a red‑black tree when a bucket becomes too large.
Read operations in JDK8 do not use any synchronization, so they are fully concurrent. Write operations follow the same logic as HashMap but add CAS and synchronized to ensure atomicity and handle resizing. The lock is now fine‑grained to the individual table slot.
The get method reads without locking, relying on the volatile nature of the value and next fields to guarantee visibility.
3. Concurrency Basics
The purpose of concurrent programming is to make full use of CPU resources. Multithreading is not always faster than single‑threaded execution; the actual benefit depends on the task.
3.1 Process vs Thread
A process is the smallest unit the operating system schedules and allocates resources to. A thread is a smaller execution unit within a process, sharing the process’s memory space.
3.2 Concurrency vs Parallelism
Concurrency : multiple threads operate on the same resource, with rapid context switches on a single‑core CPU. Parallelism : multiple CPUs execute tasks simultaneously.
3.3 Thread States
Java defines six thread states: New, Runnable, Running, Blocked, Waiting/Timed_Waiting, and Terminated.
3.4 Blocked vs Waiting
Blocked occurs when a thread attempts to acquire a synchronized lock that is held by another thread. Waiting occurs when a thread calls Object.wait() and releases the monitor, waiting for a notification.
3.5 yield vs sleep
Both pause the current thread without releasing the lock. sleep specifies a time duration; yield yields the processor’s time slice and may resume immediately.
3.6 wait vs sleep
wait is defined in Object, releases the monitor, and must be called inside a synchronized block. sleep is defined in Thread, does not release the monitor, and throws InterruptedException.
3.7 Thread Creation Methods
Extend Thread and override run().
Implement Runnable and pass it to a Thread.
Implement Callable, wrap with FutureTask, and pass to a Thread.
Use a thread pool.
Use Spring’s @Async annotation.
3.8 Deadlock
A deadlock occurs when two or more threads hold resources the other needs, and none can proceed.
Four necessary conditions: mutual exclusion, hold‑and‑wait, no preemption, and circular wait.
4. Java Memory Model (JMM)
4.1 Origin
CPU, memory, and disk speed differences lead to caches (L1/L2/L3) and consequently to cache‑coherency and memory‑visibility problems, as well as instruction reordering.
Reordering can be performed by the compiler, the processor, or the memory system.
These issues affect atomicity, visibility, and ordering, which are addressed by the JMM.
4.2 Memory Barriers
Memory barriers are CPU instructions that prevent certain reorderings and ensure visibility. Volatile variables use memory barriers internally.
4.3 happens‑before
The happens‑before relation guarantees that the result of one action is visible to another. It is enforced by program order, monitor unlock/lock, volatile write/read, thread start/join, and other rules.
4.4 as‑if‑serial
Even with reordering, a single‑threaded program must appear to execute statements in program order.
5. volatile
volatile guarantees visibility but not atomicity. Writes flush the local cache to main memory; reads invalidate the local cache and fetch the latest value.
Four types of memory barriers are used: StoreStore, StoreLoad, LoadLoad, and LoadStore.
6. Singleton (DCL + volatile)
6.1 Standard implementation
public class SingleDcl {
private volatile static SingleDcl instance;
private SingleDcl() {}
public static SingleDcl getInstance() {
if (instance == null) {
synchronized (SingleDcl.class) {
if (instance == null) {
instance = new SingleDcl();
}
}
}
return instance;
}
}volatile prevents instruction reordering that could expose a partially constructed object.
6.2 Why volatile
Without volatile, the write of the instance reference could be reordered before the constructor finishes, allowing another thread to see a half‑initialized object.
7. Thread Pool
7.1 Five‑minute overview
A thread pool has seven key parameters:
corePoolSize – number of core threads (always kept alive).
maximumPoolSize – maximum number of threads.
keepAliveTime – idle time before excess threads are terminated.
TimeUnit – unit for keepAliveTime.
BlockingQueue – task queue.
threadFactory – creates new threads.
RejectedExecutionHandler – policy when the queue is full and the pool cannot grow.
7.2 Correct creation
Using Executors factories can cause OOM because LinkedBlockingQueue may be unbounded. Create a ThreadPoolExecutor directly and specify a bounded queue.
private static ExecutorService executor = new ThreadPoolExecutor(
10, 10, 60L, TimeUnit.SECONDS,
new ArrayBlockingQueue<>(10));7.3 Common pools
Executors.newFixedThreadPool
Executors.newSingleThreadExecutor
Executors.newCachedThreadPool
Executors.newScheduledThreadPool
ThreadPoolExecutor
7.4 Core points
Understanding corePoolSize, maximumPoolSize, queue type, rejection policies, and how to size a pool for I/O‑bound vs CPU‑bound workloads is essential for interview questions.
8. ThreadLocal
ThreadLocal provides a variable that is local to each thread. Internally each Thread holds a ThreadLocalMap where the key is a weak reference to the ThreadLocal object, allowing the map entry to be reclaimed when the ThreadLocal is no longer referenced.
When get() or set() is called, the map periodically removes stale entries, preventing memory leaks of the stored values.
9. CAS
Compare‑And‑Swap is an atomic instruction that updates a variable only if it currently holds an expected value. It suffers from the ABA problem, high CPU cost under contention, and is limited to single‑variable updates.
10. synchronized
10.1 Overview
synchronized can protect instance methods, static methods, or code blocks, guaranteeing mutual exclusion, visibility, and ordering (but not preventing reordering).
10.2 Implementation
In JDK6 and later the lock can be in four states: no‑lock, biased, lightweight, and heavyweight. The lock upgrades as contention increases and never downgrades, except that a biased lock can be revoked.
10.3 Lock upgrade table
Lock State
Advantage
Disadvantage
Suitable Scenario
Biased
Zero overhead when there is no contention.
Revocation cost when contention appears.
Almost no other thread competes.
Lightweight
Spins instead of blocking, fast response.
CPU waste if it spins too long.
Few threads, short lock hold time.
Heavyweight
Threads block, no CPU spin.
Longer latency.
Many contending threads, long lock hold.
10.4 Ordering
synchronized establishes a monitorenter/monitorexit pair, which creates a happens‑before edge, ensuring that actions within the synchronized block appear in program order to other threads.
10.5 wait vs notify
wait releases the monitor and places the thread in the wait set; notify/notifyAll moves waiting threads back to the lock pool, but they cannot run until the monitor is released.
11. AQS
11.1 Thread alternation example
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
public class Main {
private static Lock lock = new ReentrantLock();
private static Condition c1 = lock.newCondition();
private static Condition c2 = lock.newCondition();
private static CountDownLatch start = new CountDownLatch(1);
public static void main(String[] args) {
char[] letters = "ABCDEFGHI".toCharArray();
char[] numbers = "123456789".toCharArray();
Thread t1 = new Thread(() -> {
try {
lock.lock();
start.countDown();
for (char ch : letters) {
c1.signal();
System.out.print(ch);
c2.await();
}
c1.signal();
} catch (InterruptedException e) { }
finally { lock.unlock(); }
});
Thread t2 = new Thread(() -> {
try {
start.await();
lock.lock();
for (char ch : numbers) {
c2.signal();
System.out.print(ch);
c1.await();
}
c2.signal();
} catch (InterruptedException e) { }
finally { lock.unlock(); }
});
t1.start();
t2.start();
}
}11.2 AQS internals
AQS uses a volatile state field updated with CAS. Threads that fail to acquire the state are enqueued in a FIFO sync queue. Conditions have a separate wait queue; await() moves the thread to the condition queue, and signal() transfers it back to the sync queue.
12. Threading Tips
Prefer stack‑confined variables to avoid sharing.
Prevent thread starvation by ensuring fair resource allocation and limiting lock hold time.
Follow a systematic performance tuning process: correctness first, then profiling, then apply Amdahl’s law.
Use read‑write locks for read‑heavy workloads, CAS for simple atomic updates, and JDK‑provided concurrent collections whenever possible.
13. End
Typical interview questions include differences between synchronized and ReentrantLock, lock state upgrades, CAS drawbacks, volatile implementation, object memory layout, double‑checked locking, as‑if‑serial vs happens‑before, ThreadLocal memory‑leak mitigation, and thread‑pool configuration.
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.
Su San Talks Tech
Su San, former staff at several leading tech companies, is a top creator on Juejin and a premium creator on CSDN, and runs the free coding practice site www.susan.net.cn.
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.
