Lock Mechanisms in Java: A Comparison between ReentrantLock and synchronized
Description
In multithreaded programming, locks are the core tool for ensuring thread safety. Java provides two main lock mechanisms: the synchronized keyword (intrinsic lock) and ReentrantLock (explicit lock). Although both can achieve synchronization, they have significant differences in design philosophy, functional flexibility, and underlying implementation. Understanding their distinctions helps in making appropriate choices for practical scenarios.
Step-by-Step Explanation of Key Points
1. Basic Concepts: The Role of Locks and Reentrancy
- Core Function of Locks: To ensure that only one thread can access a shared resource at a time, preventing data races.
- Reentrancy: Refers to the ability of a thread to reacquire a lock it already holds. For example, in recursive methods or when a synchronized method calls another synchronized method, a non-reentrant lock would cause the thread to block itself. Both
synchronizedandReentrantLockare reentrant locks.// Example of synchronized reentrancy public synchronized void methodA() { methodB(); // The thread can directly enter methodB's synchronized block without blocking } public synchronized void methodB() { }
2. Detailed Explanation of the synchronized Keyword
- Usage:
- Modifying instance methods: The lock object is the current instance (
this). - Modifying static methods: The lock object is the Class object of the current class (e.g.,
MyClass.class). - Synchronized code blocks: Requires explicitly specifying the lock object (e.g.,
synchronized(obj)).
- Modifying instance methods: The lock object is the current instance (
- Characteristics:
- JVM Built-in Implementation: No manual locking/unlocking is required; managed by the JVM via monitors.
- Automatic Lock Release: The JVM automatically releases the lock when the thread finishes executing the synchronized code or if an exception occurs.
- Non-interruptible Wait: A thread waiting for a lock cannot be interrupted, which may make deadlocks difficult to resolve.
3. Detailed Explanation of the ReentrantLock Explicit Lock
- Usage: Requires explicitly creating a lock object and manually calling
lock()andunlock().ReentrantLock lock = new ReentrantLock(); lock.lock(); try { // Critical section code } finally { lock.unlock(); // Must be placed in finally to ensure lock release } - Core Advantages:
- Interruptible Wait: Through the
lockInterruptibly()method, a thread waiting for a lock can respond to interrupts. - Fair Lock Option: Passing
trueto the constructor creates a fair lock (allocates locks based on waiting time); defaults to non-fair lock (higher throughput). - Condition Variables (Condition): Supports multiple waiting queues. For example, in a producer-consumer model, threads waiting for "queue full" and "queue empty" can be managed separately.
Condition notEmpty = lock.newCondition(); Condition notFull = lock.newCondition();
- Interruptible Wait: Through the
4. Feature Comparison and Selection Strategy
| Feature | synchronized | ReentrantLock |
|---|---|---|
| Lock Acquisition | Implicit, JVM-managed | Explicit, code-controlled |
| Interrupt Response | Not supported | Supports lockInterruptibly() |
| Fair Lock | Non-fair (JVM-optimized) | Configurable fairness |
| Condition Variables | Only via wait()/notify() |
Supports multiple Conditions |
| Lock Release | Automatic release | Must be manually unlocked, otherwise deadlock may occur |
Selection Advice:
- Prefer
synchronized: Code is concise, and the JVM continuously optimizes its performance (e.g., lock escalation mechanism). - Choose
ReentrantLockfor advanced features: Such as fair locks, interruptibility, and precise condition control.
5. Brief Analysis of Underlying Implementation Principles
- synchronized:
- Uses the object header's Mark Word to record lock status (unlocked, biased, lightweight, heavyweight).
- Lock escalation process: Biased lock (single thread) → Lightweight lock (minor contention) → Heavyweight lock (intense contention).
- ReentrantLock:
- Based on AQS (AbstractQueuedSynchronizer) queue synchronizer, maintains thread queuing via CAS operations.
- Non-fair locks attempt direct preemption; fair locks first check if there are waiting threads in the queue.
Summary
synchronized is Java's native lightweight synchronization tool, suitable for most simple scenarios; ReentrantLock provides a more flexible API, suitable for complex concurrency control. Understanding the underlying mechanisms and applicable scenarios of both is fundamental to writing efficient and robust multithreaded code.