Java Multithreading & Concurrency Mechanics
Multithreading allows Java applications to execute multiple tasks concurrently, maximizing CPU core utilization and hardware throughput. However, concurrent execution introduces complex challenges: shared mutable state, race conditions, memory visibility issues, and deadlocks. To build concurrent systems, developers must understand thread lifecycles, JVM object monitor locks, and inter-thread communication mechanisms.
In this comprehensive guide, we analyze thread state transitions, dissect JVM object monitor mechanics (EntrySet vs. WaitSet), contrast object-level vs. class-level locks, and detail deadlock prevention strategies.
1. Java Thread Lifecycle & State Machine
In Java, a thread is represented by an instance of java.lang.Thread. The JVM maps Java threads to operating system kernel threads (1:1 threading model). A Java thread transitions across six explicit states defined in java.lang.Thread.State.
stateDiagram-v2
[*] --> NEW: new Thread()
NEW --> RUNNABLE: thread.start()
RUNNABLE --> BLOCKED: Waiting for synchronized monitor lock
BLOCKED --> RUNNABLE: Lock acquired
RUNNABLE --> WAITING: wait() / join() / LockSupport.park()
WAITING --> RUNNABLE: notify() / notifyAll() / unpark()
RUNNABLE --> TIMED_WAITING: Thread.sleep(ms) / wait(ms)
TIMED_WAITING --> RUNNABLE: Time elapsed / interrupted
RUNNABLE --> TERMINATED: Run method completes or uncaught exception
Thread States Explained
- NEW: The
Threadobject has been instantiated, butstart()has not yet been invoked. - RUNNABLE: The thread is executing inside the JVM. It may be actively consuming CPU cycles or waiting for operating system CPU scheduler dispatch.
- BLOCKED: The thread is waiting to acquire a JVM object monitor lock to enter or re-enter a
synchronizedblock/method. - WAITING: The thread is waiting indefinitely for another thread to perform a specific action (e.g., calling
Object.wait()orThread.join()). - TIMED_WAITING: The thread is waiting for a specified timeout period (e.g.,
Thread.sleep(1000)orObject.wait(5000)). - TERMINATED: Execution has finished or terminated due to an uncaught exception.
2. JVM Object Monitors: EntrySet vs. WaitSet
Every object in Java has an associated intrinsic lock or monitor lock. The monitor controls mutual exclusion for synchronized blocks.
flowchart LR
subgraph ObjectMonitor["JVM Object Monitor Structure"]
Owner["Owner Thread\n(Currently executing synchronized code)"]
EntrySet["EntrySet Queue\n(Threads waiting to acquire monitor lock - BLOCKED)"]
WaitSet["WaitSet Queue\n(Threads that called wait() - WAITING)"]
end
Threads["Arriving Threads"] --> EntrySet
EntrySet --> Owner
Owner -- "wait()" --> WaitSet
WaitSet -- "notify()" --> EntrySet
Inter-Thread Communication with wait(), notify(), and notifyAll()
wait(): Executed by the thread currently owning the monitor. The thread immediately releases the lock, transitions to theWAITINGstate, and moves into the monitor's WaitSet queue.notify(): Selects an arbitrary thread from the monitor's WaitSet and moves it to the EntrySet. The woken thread transitions toBLOCKEDand competes to re-acquire the monitor lock.notifyAll(): Moves all threads from the WaitSet to the EntrySet, preventing thread starvation bugs.
3. Object-Level Lock vs. Class-Level Lock
Synchronization scope depends on the target instance or reference used for locking.
public class SharedResource {
// 1. Object-Level Lock (Locks current instance 'this')
public synchronized void instanceMethod() {
// Only ONE thread can execute this method per SharedResource object instance
}
public void blockInstanceMethod() {
synchronized(this) { // Explicit object-level lock
// Critical section
}
}
// 2. Class-Level Lock (Locks java.lang.Class object in Metaspace)
public static synchronized void staticMethod() {
// Locks SharedResource.class; blocks ALL threads across ALL object instances!
}
public void blockClassMethod() {
synchronized(SharedResource.class) { // Explicit class-level lock
// Critical section across all instances
}
}
}
4. Deadlocks & Concurrency Pitfalls
A deadlock occurs when two or more threads are blocked forever, each waiting for a lock held by the other.
flowchart LR
ThreadA["Thread A\n(Holds Lock 1)"] -- "Waiting for Lock 2" --> Lock2["Lock 2"]
ThreadB["Thread B\n(Holds Lock 2)"] -- "Waiting for Lock 1" --> Lock1["Lock 1"]
Lock1 --> ThreadA
Lock2 --> ThreadB
Deadlock Prevention Rules
- Global Lock Ordering: Always acquire multiple locks in a strict, uniform sequence across all threads.
- Lock Timeouts: Use explicit lock primitives like
java.util.concurrent.locks.ReentrantLock.tryLock(timeout, timeUnit)instead of intrinsicsynchronizedblocks to prevent indefinite blocking. - Daemon Threads: Use daemon threads (
thread.setDaemon(true)) for background tasks (e.g., GC monitoring or cleanup), as the JVM terminates automatically when all non-daemon user threads finish.
- To explore thread safe collections and ConcurrentHashMap locking, read Java Collections Framework Deep Dive.
- To examine how JVM threads map to OS threads and volatile memory barriers, read JVM Threading Model & Object Monitors.
- To master modern lightweight concurrency in Java 21+, read Virtual Threads & Project Loom Mechanics.