Why Use Locks? Deep Dive into Java Synchronization, Volatile, and AQS
This article explains why locks are needed in concurrent programming, explores the fundamentals of volatile and synchronized, details monitor mechanisms, lock optimizations like biased and lightweight locks, compares CAS and AQS, and illustrates Java lock implementations with diagrams and code examples.
Why Use Locks?
Locks are used to solve dirty reads and data inconsistency caused by concurrent operations.
Basic Principles of Lock Implementation
volatile
Java allows threads to access shared variables; to ensure accurate and consistent updates, threads should acquire exclusive locks. The volatile keyword provides visibility of shared variables across threads, making it more convenient than locks in some cases.
When a thread modifies a volatile variable, other threads can see the updated value.
Conclusion: Proper use of volatile can be cheaper than synchronized because it avoids thread context switches.
synchronized
synchronizedachieves synchronization through a locking mechanism.
Every Java object can serve as a lock. There are three forms:
For ordinary synchronized methods, the lock is the current instance.
For static synchronized methods, the lock is the Class object.
For synchronized blocks, the lock is the object specified in the parentheses.
A thread must acquire the lock before entering a synchronized block and must release it upon exit or exception.
synchronized implementation principle
synchronizedis based on a Monitor.
The Monitor supports two aspects of thread synchronization:
Mutual exclusion
Cooperation
Java uses object locks (via synchronized) to guarantee mutual exclusion on shared data, and uses notify / notifyAll / wait for coordination. Both Class and Object are associated with a Monitor.
Monitor workflow
Thread enters a synchronized method.
To execute the critical section, the thread must acquire the Monitor lock; if successful, it becomes the owner.
The owning thread can call wait() to enter the wait set and release the monitor.
Other threads call notify() or notifyAll() to wake waiting threads, which must reacquire the monitor before proceeding.
When the synchronized method finishes, the thread exits the critical section and releases the monitor.
synchronized concrete implementation
Synchronization blocks are implemented with the monitorenter and monitorexit bytecode instructions; synchronized methods use the ACC_SYNCHRONIZED flag.
Lock storage
Lock metadata is stored in the Mark Word of a Java object header.
Lock optimizations
Since Java SE 1.6, the JVM introduces biased locks and lightweight locks to reduce lock acquisition and release overhead.
Four lock states exist: no lock, biased lock, lightweight lock, and heavyweight lock. Locks can be upgraded but not downgraded.
Biased lock
Introduced to reduce overhead when there is no lock contention.
Lightweight lock
Suitable for scenarios where threads alternate executing synchronized blocks.
Lock coarsening
Combines 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
Spin duration is adjusted based on previous spin success and the state of the lock owner.
CAS (Compare-And-Set)
CAS is an atomic operation that compares a memory location's value and, if equal, updates it to a new value, ensuring updates are based on the latest information.
In the JVM, CAS is implemented using the processor's CMPXCHG instruction.
Advantages: low overhead when contention is low.
Disadvantages: high cost under long spin loops, ABA problem, and it only works for a single shared variable.
Java Lock Implementations
AbstractQueuedSynchronizer (AQS)
AQS provides a framework for building locks and other synchronizers.
It uses an int field to represent synchronization state and a FIFO double-ended queue to manage threads waiting for the lock.
Threads that fail to acquire the lock create a node and append it to the queue tail; the queue follows FIFO order.
Exclusive vs Shared locks
Exclusive lock: only one thread can hold the lock (e.g., ReentrantLock).
Shared lock: multiple threads can hold the lock simultaneously (e.g., CountDownLatch).
Practical Example: ConcurrentHashMap
ConcurrentHashMap employs lock striping: the data is divided into segments, each protected by its own lock, allowing concurrent access to different segments.
Conclusion: By using segmented locks, ConcurrentHashMap achieves high concurrency while maintaining thread safety.
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.
21CTO
21CTO (21CTO.com) offers developers community, training, and services, making it your go‑to learning and service platform.
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.
