Fundamentals 13 min read

Why Use Locks in Java? Understanding volatile, synchronized, and CAS

This article explains why Java uses locks to prevent dirty reads and data inconsistency, compares volatile and synchronized mechanisms, details monitor-based lock implementation, lock optimizations like biased and lightweight locks, introduces CAS and AbstractQueuedSynchronizer, and illustrates these concepts with code examples and diagrams.

Programmer DD
Programmer DD
Programmer DD
Why Use Locks in Java? Understanding volatile, synchronized, and CAS

1. Why use locks?

Locks are used to solve dirty reads and data inconsistency caused by concurrent operations.

2. Basic principles of lock implementation

2.1 volatile

Java allows threads to access shared variables; to ensure accurate and consistent updates, threads should acquire an exclusive lock for the variable. Java provides volatile, which in some cases is more convenient than locks. volatile guarantees visibility of shared variables in multi‑processor development; when one thread modifies a shared variable, another thread can read the new value.

Conclusion: When used correctly, volatile has lower execution cost than synchronized because it does not trigger thread context switches.

2.2 synchronized

synchronized achieves synchronization through a lock mechanism.

In Java, every object can serve as a lock. There are three forms:

For a normal synchronized method, the lock is the current instance object.

For a static synchronized method, the lock is the Class object of the current class.

For a synchronized block, the lock is the object specified in the parentheses.

When a thread attempts to execute a synchronized block, it must first obtain the lock and must release it upon exiting or throwing an exception.

2.2.1 synchronized implementation principle

synchronized is based on Monitor to achieve synchronization.

Monitor supports thread synchronization in two aspects:

Mutual exclusion execution

Cooperation

1. Java uses object locks (obtained via synchronized) to guarantee mutual exclusion on shared data sets.

2. notify/notifyAll/wait methods are used to coordinate work between different threads.

3. Both Class and Object are associated with a Monitor.

2.2.2 Details of synchronized implementation

1. Synchronized code blocks are explicitly implemented using monitorenter and monitorexit instructions.

2. Synchronized methods use the ACC_SYNCHRONIZED flag for implicit implementation.

Example implementation:

public
class
SynchronizedTest
{
public
synchronized
void
method1(){
System.out.println("Hello World!");
}
public
void
method2(){
synchronized (this){
System.out.println("Hello World!");
}
}
}

Javap compiled bytecode shows monitorenter and monitorexit instructions.

monitorenter

Each object has a monitor; only one thread can own it at a time. When a thread executes monitorenter, it tries to acquire the monitor according to these rules:

If the monitor entry count is 0, the thread can enter and the count becomes 1; the thread becomes the monitor owner.

If the current thread already owns the monitor and re‑enters, the entry count increments; the lock is re‑entrant.

If the monitor is owned by another thread, the current thread blocks until the entry count returns to 0.

monitorexit

Only the thread that owns the monitor can execute monitorexit. Each execution decrements the entry count; when it reaches 0, the thread releases the monitor, allowing other blocked threads to attempt acquisition.

2.2.3 Where the lock is stored

The lock mark is stored in the Mark Word of the Java object header.

2.2.4 Lock optimizations

Since Java SE 1.6, biased locks and lightweight locks were introduced to reduce the performance cost of acquiring and releasing locks.

Four lock states exist: no lock, biased lock, lightweight lock, heavyweight lock, which upgrade with contention but never downgrade.

Biased lock

In the absence of lock competition, biased lock reduces lock overhead.

Lightweight lock

Lightweight lock is suitable for scenarios where threads alternately execute synchronized blocks.

Lock coarsening : merging multiple adjacent lock/unlock operations into a larger lock region.

Lock elimination : the JIT compiler removes locks that are proven to have no shared data contention.

Adaptive spinning : the spin duration is adjusted based on previous lock acquisition success and the state of the lock owner.

2.2.5 Pros and cons of locks

2.3 CAS

CAS (Compare And Swap) is an atomic operation that compares a memory location's value and, if equal, updates it to a new value, ensuring the new value is based on the latest information.

1. CAS has low overhead when contention is low.

2. In the JVM, CAS is implemented using the processor's CMPXCHG instruction.

Advantages:

Low system overhead when contention is low.

Disadvantages:

Long loop time incurs high overhead.

ABA problem.

Only guarantees atomicity for a single shared variable.

3. Java lock implementations

3.1 AbstractQueuedSynchronizer (AQS)

AQS is a foundational framework for building locks and other synchronizers.

3.1.1 It uses an int field to represent synchronization state.

3.1.2 It uses an internal FIFO double‑ended queue to manage threads waiting for the lock.

The synchronizer maintains head and tail nodes; threads that fail to acquire the lock create a node and safely add it to the tail via compareAndSetTail. The queue follows FIFO order, and the head node holds the lock.

Threads that fail to acquire the lock create a node and set it as the tail node.

The head node thread, upon releasing the lock, wakes up its successor, which then becomes the new head after acquiring the lock.

3.1.3 Exclusive vs. shared lock acquisition

Exclusive: only one thread can acquire the lock (e.g., ReentrantLock). Shared: multiple threads can acquire the lock simultaneously (e.g., CountDownLatch).

Exclusive

Each node spins to check if its predecessor is the head; if so, it attempts to acquire the lock.

Exclusive lock acquisition flow diagram.

Shared

Difference between shared and exclusive locks.

4. Lock usage examples

4.1 ConcurrentHashMap implementation (Java 1.7)

ConcurrentHashMap uses segment locking: data is divided into segments, each protected by its own lock, allowing threads to access different segments concurrently.

Original Source

Signed-in readers can open the original source through BestHub's protected redirect.

Sign in to view source
Republication Notice

This article has been distilled and summarized from source material, then republished for learning and reference. If you believe it infringes your rights, please contactadmin@besthub.devand we will review it promptly.

JavaconcurrencyCASvolatileLocksAQSsynchronized
Programmer DD
Written by

Programmer DD

A tinkering programmer and author of "Spring Cloud Microservices in Action"

0 followers
Reader feedback

How this landed with the community

Sign in to like

Rate this article

Was this worth your time?

Sign in to rate
Discussion

0 Comments

Thoughtful readers leave field notes, pushback, and hard-won operational detail here.