synchronized 和 ReentrantLock 是 Java 中实现线程同步的两种核心机制,它们在用法、功能和底层实现上有显著差异,以下从 区别 和 底层原理 两方面详细说明:
一、核心区别
| 对比维度 | synchronized |
ReentrantLock |
|---|---|---|
| 锁的类型 | 隐式锁(JVM 自动管理锁的获取和释放) | 显式锁(需手动调用 lock() 和 unlock()) |
| 可中断性 | 不可中断(一旦进入阻塞,无法被其他线程中断) | 可中断(支持 lockInterruptibly() 响应中断) |
| 公平性 | 非公平锁(默认,无法设置为公平锁) | 可指定公平 / 非公平(构造函数传入 true 为公平锁) |
| 条件变量 | 不支持(仅通过 wait()/notify() 实现简单等待唤醒) |
支持(通过 newCondition() 获取 Condition 对象,可实现多条件等待) |
| 锁绑定多个条件 | 不支持(一个锁只能对应一个等待队列) | 支持(一个锁可创建多个 Condition,对应多个等待队列) |
| 性能 | JDK 1.6 后优化(偏向锁、轻量级锁),性能接近 ReentrantLock |
早期版本性能优于 synchronized,JDK 1.6 后两者接近 |
| 使用场景 | 简单同步场景(如单条件同步、方法 / 代码块同步) | 复杂同步场景(如中断控制、公平锁、多条件等待) |
二、底层原理
1. synchronized 的底层原理
synchronized 是 JVM 内置的同步机制,底层通过 对象头(Mark Word) 和 监视器锁(Monitor) 实现,具体依赖 JVM 指令(monitorenter/monitorexit)。
-
对象头(Mark Word) :Java 对象在内存中的布局包含 对象头 ,其中
Mark Word存储对象的锁状态(无锁、偏向锁、轻量级锁、重量级锁)。当线程竞争synchronized锁时,JVM 会通过修改Mark Word的锁状态标识线程持有锁的情况。 -
锁升级过程(JDK 1.6 优化) :为减少锁竞争的开销,
synchronized采用 锁升级 策略,从低开销到高开销逐步升级:- 无锁状态 :对象刚创建时,
Mark Word记录哈希码等信息,无锁竞争。 - 偏向锁 :若只有一个线程获取锁,JVM 会在
Mark Word中记录该线程 ID,后续该线程可直接获取锁(无需 CAS 操作),减少开销。 - 轻量级锁:当有其他线程竞争时,偏向锁升级为轻量级锁,线程通过 CAS 操作尝试获取锁(自旋等待,不阻塞),适合短时间竞争。
- 重量级锁 :若竞争激烈(自旋失败),轻量级锁升级为重量级锁,依赖操作系统的 互斥量(Mutex) 实现,线程会进入内核态阻塞(开销大),此时关联一个 Monitor(监视器) 管理锁的获取和释放。
- 无锁状态 :对象刚创建时,
-
Monitor 监视器 :重量级锁的核心是
Monitor,每个对象都关联一个Monitor(通过ObjectMonitor实现),包含 EntryList(等待锁的线程队列) 、Owner(持有锁的线程) 、WaitSet(调用wait()等待的线程队列) 。线程通过monitorenter尝试获取Monitor的所有权(成功则成为Owner,失败则进入EntryList阻塞),通过monitorexit释放所有权(唤醒EntryList中的线程竞争)。
2. ReentrantLock 的底层原理
ReentrantLock 是 JDK 提供的工具类(位于 java.util.concurrent.locks),底层基于 AQS(AbstractQueuedSynchronizer,抽象队列同步器) 实现,是一种 显式锁。
-
AQS 核心结构 :AQS 是并发工具的基础框架,内部维护一个 volatile 修饰的 state 变量 (表示锁的状态,0 为未锁定,>0 为已锁定,支持重入时累加)和一个 双向链表(等待队列)(存储阻塞的线程)。
-
加锁过程:
- 线程调用
lock()时,通过 CAS 尝试修改state变量:- 若
state=0(未锁定),将state设为 1,记录当前线程为所有者,加锁成功。 - 若
state>0且当前线程是所有者(重入),将state加 1,加锁成功。 - 若加锁失败,当前线程被封装为 Node 节点,加入 AQS 等待队列,进入阻塞状态。
- 若
- 线程调用
-
解锁过程:
- 线程调用
unlock()时,将state减 1:- 若
state减为 0,释放锁,唤醒等待队列中的线程(通过 LockSupport.park/unpark 控制线程阻塞 / 唤醒)。 - 若
state>0(重入未完全释放),仅更新state,不释放锁。
- 若
- 线程调用
-
公平锁与非公平锁:
- 非公平锁(默认):线程加锁时直接尝试 CAS 获取锁,无视等待队列中的线程,可能导致线程饥饿,但性能更高。
- 公平锁:线程加锁时需先检查等待队列,若有线程排队则当前线程进入队列尾部,保证按顺序获取锁,但性能略低(因需维护队列顺序)。
-
条件变量(Condition) :
ReentrantLock通过newCondition()创建Condition对象,每个Condition对应一个 等待队列 (与 AQS 主队列分离)。调用await()会将线程加入 Condition 等待队列并释放锁,调用signal()会将线程从 Condition 队列移到 AQS 主队列参与锁竞争,实现多条件等待。
三、synchronized 示例
synchronized 是隐式锁,通过修饰方法或代码块实现同步,由 JVM 自动管理锁的获取和释放。
java
public class SynchronizedDemo {
// 共享资源
private int count = 0;
// 1. 修饰方法(锁为当前对象实例)
public synchronized void increment() {
count++;
}
// 2. 修饰代码块(锁为指定对象,这里用this)
public void decrement() {
synchronized (this) {
count--;
}
}
public int getCount() {
return count;
}
public static void main(String[] args) throws InterruptedException {
SynchronizedDemo demo = new SynchronizedDemo();
// 多线程操作共享资源
Thread t1 = new Thread(() -> {
for (int i = 0; i < 1000; i++) {
demo.increment();
}
});
Thread t2 = new Thread(() -> {
for (int i = 0; i < 1000; i++) {
demo.decrement();
}
});
t1.start();
t2.start();
t1.join();
t2.join();
System.out.println("最终结果:" + demo.getCount()); // 预期为 0(线程安全)
}
}
特点:
- 无需手动释放锁(方法 / 代码块执行完自动释放)。
- 不可中断(若线程阻塞在
synchronized处,无法被其他线程中断)。
四、ReentrantLock 示例
ReentrantLock 是显式锁,需手动调用 lock() 获取锁、unlock() 释放锁(通常在 try-finally 中确保释放),支持更多高级特性。
示例 1:基本用法(非公平锁,默认)
java
import java.util.concurrent.locks.ReentrantLock;
public class ReentrantLockDemo {
private int count = 0;
// 创建锁(默认非公平锁,公平锁需传 true:new ReentrantLock(true))
private final ReentrantLock lock = new ReentrantLock();
public void increment() {
// 手动获取锁
lock.lock();
try {
count++; // 临界区
} finally {
// 手动释放锁(必须放在 finally 中,避免异常导致锁未释放)
lock.unlock();
}
}
public void decrement() {
lock.lock();
try {
count--;
} finally {
lock.unlock();
}
}
public int getCount() {
return count;
}
public static void main(String[] args) throws InterruptedException {
ReentrantLockDemo demo = new ReentrantLockDemo();
Thread t1 = new Thread(() -> {
for (int i = 0; i < 1000; i++) {
demo.increment();
}
});
Thread t2 = new Thread(() -> {
for (int i = 0; i < 1000; i++) {
demo.decrement();
}
});
t1.start();
t2.start();
t1.join();
t2.join();
System.out.println("最终结果:" + demo.getCount()); // 预期为 0(线程安全)
}
}
示例 2:高级特性(可中断、条件变量)
体现 ReentrantLock 相比 synchronized 的独特功能:
java
import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.ReentrantLock;
public class ReentrantLockAdvancedDemo {
private final ReentrantLock lock = new ReentrantLock();
// 创建两个条件变量(类似两个等待队列)
private final Condition notEmpty = lock.newCondition();
private final Condition notFull = lock.newCondition();
private int[] buffer = new int[10]; // 缓冲区
private int size = 0; // 缓冲区元素数量
// 生产者:向缓冲区添加元素
public void put(int value) throws InterruptedException {
lock.lockInterruptibly(); // 可中断的加锁(区别于 lock())
try {
// 若缓冲区满,等待 notFull 信号
while (size == buffer.length) {
notFull.await(); // 释放锁,进入 notFull 等待队列
}
buffer[size++] = value;
System.out.println("生产:" + value + ",当前 size:" + size);
notEmpty.signal(); // 唤醒等待 notEmpty 的线程(消费者)
} finally {
lock.unlock();
}
}
// 消费者:从缓冲区取元素
public int take() throws InterruptedException {
lock.lockInterruptibly();
try {
// 若缓冲区空,等待 notEmpty 信号
while (size == 0) {
notEmpty.await(); // 释放锁,进入 notEmpty 等待队列
}
int value = buffer[--size];
System.out.println("消费:" + value + ",当前 size:" + size);
notFull.signal(); // 唤醒等待 notFull 的线程(生产者)
return value;
} finally {
lock.unlock();
}
}
public static void main(String[] args) throws InterruptedException {
ReentrantLockAdvancedDemo demo = new ReentrantLockAdvancedDemo();
// 启动生产者线程
Thread producer = new Thread(() -> {
try {
for (int i = 0; i < 15; i++) {
demo.put(i); // 生产 15 个元素(缓冲区最大 10,会阻塞等待)
}
} catch (InterruptedException e) {
System.out.println("生产者被中断");
}
});
// 启动消费者线程
Thread consumer = new Thread(() -> {
try {
for (int i = 0; i < 10; i++) {
demo.take(); // 消费 10 个元素
}
} catch (InterruptedException e) {
System.out.println("消费者被中断");
}
});
producer.start();
consumer.start();
// 演示可中断性:让生产者运行 1 秒后中断
Thread.sleep(1000);
producer.interrupt(); // 触发 producer 的 lockInterruptibly() 中断
}
}
高级特性体现:
- 可中断 :通过
lockInterruptibly()允许线程在等待锁时被中断(如producer.interrupt()可终止生产者的等待)。 - 多条件变量 :
notEmpty和notFull两个Condition分别管理消费者和生产者的等待队列,实现更精细的等待唤醒控制(synchronized仅能通过wait()/notify()操作一个等待队列)。
总结
synchronized 和 ReentrantLock 是 Java 保障线程安全的核心同步机制,核心差异体现在锁管理、功能灵活性与底层实现,选择需贴合场景需求:
- 用法与功能 :
synchronized是 JVM 内置隐式锁,无需手动管理锁的获取与释放,用法简洁,仅支持基础同步,适合简单场景;ReentrantLock是 JDK 提供的显式锁,需通过lock()/unlock()手动控制(需配合finally释放),支持可中断、公平 / 非公平锁、多条件变量等高级特性,适配复杂并发场景。 - 底层实现 :
synchronized依赖对象头(Mark Word)和 Monitor 监视器,通过 "无锁→偏向锁→轻量级锁→重量级锁" 的升级策略优化性能;ReentrantLock基于 AQS(抽象队列同步器),通过state变量记录锁状态,用双向链表管理等待线程,支持灵活的锁机制。 - 性能与选择 :JDK 1.6 后
synchronized经优化,性能与ReentrantLock接近;无特殊需求时优先选synchronized(低风险、低成本),需高级功能时选用ReentrantLock(灵活可控),两者最终均通过控制临界区访问保障线程安全。