ReentrantLock 与 Synchronized 的区别
ReentrantLock 与 Synchronized 的区别
ReentrantLock 和 Synchronized 都是 Java 中用于实现线程同步的机制,但它们有显著的区别:
1. 基本性质对比
特性 | ReentrantLock | Synchronized |
---|---|---|
实现级别 | JDK 层面 (java.util.concurrent.locks) | JVM 层面 (关键字) |
锁的获取方式 | 显式调用 lock()/unlock() | 隐式获取和释放 (代码块/方法) |
灵活性 | 高 (可中断、超时、公平锁等) | 低 (基本功能) |
性能 | Java 6+ 性能相当 | Java 6+ 性能相当 |
可重入性 | 支持 | 支持 |
2. 功能区别详解
(1) 锁的获取方式
// ReentrantLock - 显式锁
ReentrantLock lock = new ReentrantLock();
lock.lock(); // 必须显式获取
try {// 临界区代码
} finally {lock.unlock(); // 必须显式释放
}// Synchronized - 隐式锁
synchronized(this) { // 自动获取和释放// 临界区代码
}
(2) 可中断性
// ReentrantLock 可响应中断
ReentrantLock lock = new ReentrantLock();
try {lock.lockInterruptibly(); // 可被中断的获取锁// 临界区代码
} catch (InterruptedException e) {// 处理中断
} finally {if(lock.isHeldByCurrentThread()) {lock.unlock();}
}// Synchronized 不可中断
synchronized(this) {// 如果其他线程不释放锁,当前线程会一直阻塞
}
(3) 公平锁支持
// ReentrantLock 可实现公平锁
ReentrantLock fairLock = new ReentrantLock(true); // true表示公平锁// Synchronized 只能是非公平锁
synchronized(this) { // 非公平// ...
}
(4) 尝试获取锁
// ReentrantLock 可尝试获取锁
if (lock.tryLock()) { // 立即返回是否成功try {// 获取锁成功} finally {lock.unlock();}
}// 带超时的尝试获取
if (lock.tryLock(1, TimeUnit.SECONDS)) { // 等待1秒try {// ...} finally {lock.unlock();}
}// Synchronized 无法实现尝试获取
(5) 条件变量 (Condition)
// ReentrantLock 支持多个条件
ReentrantLock lock = new ReentrantLock();
Condition condition = lock.newCondition();lock.lock();
try {condition.await(); // 释放锁并等待condition.signal(); // 唤醒一个等待线程
} finally {lock.unlock();
}// Synchronized 只有一个等待队列
synchronized(obj) {obj.wait(); // 等待obj.notify(); // 唤醒
}
3. 使用场景建议
使用 Synchronized 当:
- 简单的同步需求
- 不需要高级功能(如可中断、公平锁等)
- 代码简洁性更重要时
- 在 Java 5 之前的版本中
使用 ReentrantLock 当:
- 需要可中断的锁获取
- 需要尝试获取锁(带超时)
- 需要公平锁机制
- 需要多个条件变量
- 需要更细粒度的控制
4. 性能考虑
- 在 Java 5 中,ReentrantLock 性能优于 synchronized
- 从 Java 6 开始,JVM 对 synchronized 进行了大量优化,两者性能相当
- 在高度竞争的情况下,ReentrantLock 可能仍然有优势
5. 最佳实践
- 优先考虑 synchronized:在简单场景下,synchronized 更简洁且不易出错
- 需要高级功能时使用 ReentrantLock:当需要上述高级特性时选择 ReentrantLock
- 确保释放锁:使用 ReentrantLock 时必须在 finally 块中释放锁
- 避免嵌套:过度使用锁会导致死锁风险增加
总结
ReentrantLock 提供了比 synchronized 更灵活的锁操作,但随之而来的是更复杂的用法和更大的出错可能性。在大多数常见场景下,synchronized 已经足够使用且更安全。只有在确实需要 ReentrantLock 的高级功能时,才应该使用它。
ReentrantLock 与 synchronized 底层实现区别
一、synchronized 底层实现
1. JVM 层面的实现
synchronized 是 Java 语言内置的关键字,其实现完全由 JVM 负责:
-
监视器锁(Monitor)机制:
- 每个 Java 对象都有一个关联的监视器锁(Monitor)
- 通过对象头中的 Mark Word 实现锁状态记录
-
对象内存布局:
|--------------------------------------------------------------| | Object Header (64 bits) | |------------------------------------|-------------------------| | Mark Word (32/64 bits) | Klass Pointer (32 bits) | |------------------------------------|-------------------------|
-
Mark Word 结构(32位 JVM):
|-------------------------------------------------------|--------------------| | Mark Word (32 bits) | State | |-------------------------------------------------------|--------------------| | hashcode:25 | age:4 | biased_lock:1 | lock:2 (01) | Normal | |-------------------------------------------------------|--------------------| | thread:23 | epoch:2 | age:4 | biased_lock:1 | lock:2 (01) | Biased | |-------------------------------------------------------|--------------------| | ptr_to_lock_record:30 | lock:2 (00) | Lightweight Lock | |-------------------------------------------------------|--------------------| | ptr_to_heavyweight_monitor:30 | lock:2 (10) | Heavyweight Lock | |-------------------------------------------------------|--------------------| | | lock:2 (11) | Marked | |-------------------------------------------------------|--------------------|
2. 锁升级过程
synchronized 采用锁膨胀策略,根据竞争情况逐步升级:
- 无锁状态:初始状态
- 偏向锁(Biased Locking):
- 适用于只有一个线程访问同步块
- 通过 CAS 操作将线程 ID 写入 Mark Word
- 轻量级锁(Thin Lock):
- 当有轻微竞争时,转换为轻量级锁
- 使用栈中的 Lock Record 和 CAS 操作
- 重量级锁(Heavyweight Lock):
- 竞争激烈时升级为重量级锁
- 通过操作系统的互斥量(mutex)实现
- 涉及线程阻塞和唤醒,成本较高
3. 底层指令
synchronized 同步块编译后会产生:
monitorenter
指令:进入同步块monitorexit
指令:退出同步块(包括正常退出和异常退出)
二、ReentrantLock 底层实现
1. AQS(AbstractQueuedSynchronizer)框架
ReentrantLock 基于 Doug Lea 开发的 AQS 框架实现:
-
核心数据结构:
// AQS 内部类 Node 表示等待线程 static final class Node {volatile int waitStatus;volatile Node prev;volatile Node next;volatile Thread thread;Node nextWaiter; // 用于条件队列 }// AQS 关键字段 private volatile int state; // 同步状态 private transient volatile Node head; // 队首 private transient volatile Node tail; // 队尾
-
state 状态:
- 0 表示锁未被占用
-
0 表示锁被占用,数值表示重入次数
2. 获取锁流程(非公平锁为例)
-
tryLock() 尝试获取:
final boolean nonfairTryAcquire(int acquires) {final Thread current = Thread.currentThread();int c = getState();if (c == 0) { // 锁未被占用if (compareAndSetState(0, acquires)) { // CAS 尝试获取setExclusiveOwnerThread(current); // 设置当前线程为持有者return true;}}else if (current == getExclusiveOwnerThread()) { // 重入检查int nextc = c + acquires;if (nextc < 0) throw new Error("Maximum lock count exceeded");setState(nextc);return true;}return false; }
-
加入等待队列:
private Node addWaiter(Node mode) {Node node = new Node(Thread.currentThread(), mode);Node pred = tail;if (pred != null) {node.prev = pred;if (compareAndSetTail(pred, node)) { // CAS 设置尾节点pred.next = node;return node;}}enq(node); // 自旋入队return node; }
-
自旋/阻塞:
final boolean acquireQueued(final Node node, int arg) {boolean interrupted = false;try {for (;;) {final Node p = node.predecessor();if (p == head && tryAcquire(arg)) { // 前驱是头节点且尝试获取锁setHead(node);p.next = null;return interrupted;}if (shouldParkAfterFailedAcquire(p, node) &&parkAndCheckInterrupt()) // 阻塞当前线程interrupted = true;}} catch (Throwable t) {cancelAcquire(node);throw t;} }
3. 释放锁流程
protected final boolean tryRelease(int releases) {int c = getState() - releases;if (Thread.currentThread() != getExclusiveOwnerThread())throw new IllegalMonitorStateException();boolean free = false;if (c == 0) { // 完全释放free = true;setExclusiveOwnerThread(null);}setState(c); // 更新状态return free;
}
三、核心区别对比
特性 | synchronized | ReentrantLock (基于AQS) |
---|---|---|
实现层面 | JVM 内置,通过对象头和 monitor 实现 | JDK 实现,基于 CAS 和 CLH 队列 |
锁状态存储 | 对象头中的 Mark Word | AQS 中的 state 变量 |
等待队列 | JVM 维护的等待队列 | 显式的 CLH 队列(双向链表) |
竞争处理 | 锁膨胀(偏向锁→轻量级锁→重量级锁) | 直接 CAS 竞争,失败后入队 |
阻塞机制 | 重量级锁使用 OS 互斥量 | 使用 LockSupport.park() 阻塞 |
可重入实现 | 通过_recursions计数器 | 通过 state 计数器 |
性能优化 | 偏向锁、适应性自旋等 JVM 优化 | 纯 Java 实现,依赖 CAS 和 park/unpark |
中断响应 | 不支持 | 支持 lockInterruptibly() |
公平性 | 只有非公平模式 | 可选择公平/非公平模式 |
条件变量 | 只有一个 wait/notify 队列 | 可创建多个 Condition 对象 |
四、底层原理图示
synchronized 锁状态转换
[无锁] → [偏向锁] → [轻量级锁] → [重量级锁]↑____________| |撤销偏向锁 自旋失败
ReentrantLock 获取锁流程
尝试获取锁 (CAS)↓
成功? → 执行同步代码↓ No
加入CLH队列↓
自旋检查前驱节点↓
前驱是头节点? → 尝试获取锁 → 成功? → 成为新头节点↓ No ↓ No
阻塞等待唤醒 继续自旋
五、选择建议
-
优先 synchronized:
- 简单同步场景
- 不需要高级功能
- Java 6+ 性能已优化
-
选择 ReentrantLock:
- 需要可中断、超时、公平锁等高级特性
- 需要多个条件变量
- 需要更细粒度的控制
- 明确知道锁竞争情况的高并发场景
理解这些底层实现差异有助于在特定场景下做出更合适的技术选型,并能够更好地进行性能调优和问题排查。