The volatile keyword is often misunderstood as a complete thread‑safety mechanism. In reality, volatile provides only visibility and ordering guarantees; it does not ensure atomicity or prevent race conditions. When a variable is declared volatile, any write to it is immediately visible to other threads, and the JVM does not reorder memory operations on that variable.
volatile int counter;
counter++;
Although counter is volatile, the increment counter++ is not an atomic operation—it consists of reading the value, adding one, and writing it back. Because volatile cannot guarantee atomicity, concurrent threads executing this code can still lose updates.
For many years developers avoided synchronized blocks, convinced they were slow, and preferred the java.util.concurrent.locks package. Since JDK 1.6, however, JVM optimizations have dramatically improved synchronized performance. A "lock upgrade" mechanism promotes an uncontended lock through four states: unlocked → biased → lightweight → heavyweight. The JVM dynamically escalates the lock based on contention, eliminating unnecessary overhead.
In many scenarios synchronized is now faster than hand‑crafted locking and its semantics are clearer. Consider what happens if an exception is thrown inside a synchronized block:
synchronized (mutex) {
performTask();
}
The lock is automatically released! With manual locking, release must be handled explicitly, otherwise an exception can leave the lock held forever, causing a deadlock:
lock.lock();
try {
performTask();
} finally {
lock.unlock();
}
Thread.sleep() should never be used to coordinate threads. It offers no guaranteed timing, cannot synchronise state, and breaks down under heavy load or on slow machines. Reliable coordination requires proper concurrency constructs such as:
wait()/notify()CountDownLatchCyclicBarrierCompletableFuture
The differences between wait() and sleep() are fundamental:
sleep()does not release any lock and holds CPU resources;wait()releases the monitor lock.sleep()can be called anywhere;wait()must be in side asynchronizedblock or method.sleep()pauses execution (timed waiting);wait()is for inter‑thread communication.sleep()wakes up automatically after a timeout;wait()waits fornotify()ornotifyAll().
Using sleep() in a multi‑threaded system simply degrades performance.
Is the classic double‑checked locking pattern safe in a multi‑threaded environment? The following code seems correct, but it is not:
public class ResourceManager {
private static ResourceManager resource;
public static ResourceManager getResource() {
if (resource == null) {
synchronized (ResourceManager.class) {
if (resource == null) {
resource = new ResourceManager();
}
}
}
return resource;
}
}
The problem is instruction reordering. The JVM may execute resource = new ResourceManager(); non‑atomically in three steps:
- Allocate memory.
- Initialize the object (run constructor).
- Assign the reference to
resource.
Because the JVM and CPU can reorder these steps for performance, another thread might see a partially initialized object. The fix is to declare the field volatile:
public class ResourceManager {
private static volatile ResourceManager resource;
public static ResourceManager getResource() {
if (resource == null) {
synchronized (ResourceManager.class) {
if (resource == null) {
resource = new ResourceManager();
}
}
}
return resource;
}
}
Java object creation proceeds through five logical stages, executed in order by the JVM to guarantee correct instantiation:
- Class loading check: the symbolic reference from the
newinstruction is resolved and the class is loaded, linked, and initialised if necessary. - Memory allocation: a contiguous block of heap memory is allocated for the object.
- Zero initialisation: the allocated memory (excluding the object header) is filled with default zero values, so fields alreeady have their default values before the constructor runs.
- Object header setup: the header is populated with runtime data such as the hash code, GC generation age, and lock status flags.
- Execution of
<init>: the developer‑defined constructor is invoked to initialise the object according to the intended logic.