Java并发编程:深入对比synchronized与ReentrantLock
个人名片
🎓作者简介:java领域优质创作者
🌐个人主页:码农阿豪
📞工作室:新空间代码工作室(提供各种软件服务)
💌个人邮箱:[2435024119@qq.com]
📱个人微信:15279484656
🌐个人导航网站:www.forff.top
💡座右铭:总有人要赢。为什么不能是我呢?
- 专栏导航:
码农阿豪系列专栏导航
面试专栏:收集了java相关高频面试题,面试实战总结🍻🎉🖥️
Spring5系列专栏:整理了Spring5重要知识点与实战演练,有案例可直接使用🚀🔧💻
Redis专栏:Redis从零到一学习分享,经验总结,案例实战💐📝💡
全栈系列专栏:海纳百川有容乃大,可能你想要的东西里面都有🤸🌱🚀
Java并发编程:深入对比synchronized与ReentrantLock
引言
在Java多线程编程中,同步机制是确保线程安全的核心。synchronized和ReentrantLock是Java中最常用的两种同步工具,它们各有特点,适用于不同的场景。本文将深入分析这两种同步机制的实现原理、使用方式、性能差异以及适用场景,帮助开发者做出更明智的选择。
1. 基本概念对比
1.1 synchronized关键字
synchronized是Java内置的同步机制,它提供了一种简单的方式来实现线程同步:
public class SynchronizedExample {
private int count = 0;
// 同步方法
public synchronized void increment() {
count++;
}
public void incrementBlock() {
// 同步代码块
synchronized(this) {
count++;
}
}
}
1.2 ReentrantLock类
ReentrantLock是Java 5引入的显式锁机制,位于java.util.concurrent.locks包中:
import java.util.concurrent.locks.ReentrantLock;
public class ReentrantLockExample {
private int count = 0;
private final ReentrantLock lock = new ReentrantLock();
public void increment() {
lock.lock(); // 获取锁
try {
count++;
} finally {
lock.unlock(); // 释放锁
}
}
}
2. 特性对比
2.1 可重入性
两者都是可重入的,即同一个线程可以多次获取同一个锁:
// synchronized的可重入性
public synchronized void methodA() {
methodB(); // 可以调用另一个同步方法
}
public synchronized void methodB() {
// ...
}
// ReentrantLock的可重入性
public void methodA() {
lock.lock();
try {
methodB();
} finally {
lock.unlock();
}
}
public void methodB() {
lock.lock();
try {
// ...
} finally {
lock.unlock();
}
}
2.2 公平性
synchronized不提供公平性保证,而ReentrantLock可以配置为公平锁:
// 创建公平锁
ReentrantLock fairLock = new ReentrantLock(true);
公平锁会按照线程请求锁的顺序来分配锁,但会降低吞吐量。
2.3 锁的获取方式
synchronized:自动获取和释放锁,进入同步代码块时获取,退出时释放ReentrantLock:需要显式调用lock()和unlock()方法
2.4 中断响应
ReentrantLock可以响应中断,而synchronized不能:
public void interruptibleLock() throws InterruptedException {
ReentrantLock lock = new ReentrantLock();
try {
lock.lockInterruptibly(); // 可中断的获取锁
try {
// 临界区代码
} finally {
lock.unlock();
}
} catch (InterruptedException e) {
// 处理中断
}
}
2.5 条件变量
ReentrantLock可以创建多个Condition对象,而synchronized只能有一个等待队列:
public class ConditionExample {
private final ReentrantLock lock = new ReentrantLock();
private final Condition notEmpty = lock.newCondition();
private final Condition notFull = lock.newCondition();
private final Object[] items = new Object[100];
private int count;
public void put(Object x) throws InterruptedException {
lock.lock();
try {
while (count == items.length)
notFull.await(); // 等待不满条件
items[count++] = x;
notEmpty.signal(); // 唤醒等待不空条件的线程
} finally {
lock.unlock();
}
}
public Object take() throws InterruptedException {
lock.lock();
try {
while (count == 0)
notEmpty.await(); // 等待不空条件
Object x = items[--count];
notFull.signal(); // 唤醒等待不满条件的线程
return x;
} finally {
lock.unlock();
}
}
}
3. 性能对比
在Java早期版本中,ReentrantLock的性能明显优于synchronized。但随着Java版本的更新,synchronized的性能得到了显著提升,现在两者的性能差异已经不大。
在低竞争情况下,synchronized可能更优,因为它是JVM内置特性;在高竞争情况下,ReentrantLock的可配置性可能带来更好的性能。
4. 使用场景
4.1 优先使用synchronized的情况
- 简单的同步需求
- 不需要高级特性(如可中断、公平锁、多个条件变量)
- 代码简洁性更重要时
4.2 优先使用ReentrantLock的情况
- 需要可中断的锁获取操作
- 需要公平锁
- 需要多个条件变量
- 需要尝试获取锁(tryLock)
public void tryLockExample() {
ReentrantLock lock = new ReentrantLock();
if (lock.tryLock()) {
try {
// 获取锁成功,执行临界区代码
} finally {
lock.unlock();
}
} else {
// 获取锁失败,执行其他操作
}
}
5. 最佳实践
5.1 synchronized最佳实践
- 尽量缩小同步代码块的范围
- 避免在同步代码块中执行耗时操作
- 注意锁的粒度,避免不必要的同步
5.2 ReentrantLock最佳实践
- 总是在finally块中释放锁
- 考虑使用tryLock避免死锁
- 合理使用Condition实现精细的线程通信
public void transfer(Account from, Account to, int amount) {
// 按照固定顺序获取锁,避免死锁
int fromHash = System.identityHashCode(from);
int toHash = System.identityHashCode(to);
if (fromHash < toHash) {
from.lock.lock();
to.lock.lock();
} else if (fromHash > toHash) {
to.lock.lock();
from.lock.lock();
} else {
// 哈希冲突时的处理
synchronized (from) {
synchronized (to) {
// 转账操作
}
}
return;
}
try {
// 执行转账操作
} finally {
from.lock.unlock();
to.lock.unlock();
}
}
6. 底层实现
6.1 synchronized实现原理
synchronized是基于JVM层面的Monitor实现的,包含以下概念:
- 进入区(Entry Set)
- 拥有区(Owner)
- 等待区(Wait Set)
在字节码层面,同步方法通过ACC_SYNCHRONIZED标志实现,同步代码块通过monitorenter和monitorexit指令实现。
6.2 ReentrantLock实现原理
ReentrantLock是基于AQS(AbstractQueuedSynchronizer)实现的,主要包含:
- state变量表示锁的状态
- CLH队列维护等待线程
- CAS操作保证原子性
7. 选择建议
在选择synchronized和ReentrantLock时,可以考虑以下因素:
- 复杂性:简单场景用
synchronized,复杂场景用ReentrantLock - 性能:现代JVM中两者性能接近,除非有特殊需求
- 功能需求:需要高级功能时选择
ReentrantLock - 可维护性:
synchronized代码更简洁,不易出错 - 可扩展性:未来可能需要更多功能时选择
ReentrantLock
8. 总结
synchronized和ReentrantLock都是Java中强大的同步工具,各有优缺点:
| 特性 | synchronized | ReentrantLock |
|---|---|---|
| 实现方式 | JVM内置 | JDK实现 |
| 使用复杂度 | 简单 | 较复杂 |
| 可重入性 | 支持 | 支持 |
| 公平性 | 不支持 | 可配置 |
| 可中断性 | 不支持 | 支持 |
| 尝试获取锁 | 不支持 | 支持(tryLock) |
| 条件变量 | 单一 | 多个 |
| 性能 | 现代JVM优化好 | 高竞争下可能更优 |
| 异常时自动释放锁 | 支持 | 需手动释放 |
在实际开发中,应根据具体需求选择合适的同步机制。对于大多数简单场景,synchronized是更简洁安全的选择;而对于需要更高级功能的复杂场景,ReentrantLock提供了更大的灵活性。
理解这两种同步机制的原理和特性,有助于我们编写出更高效、更安全的并发程序。


浙公网安备 33010602011771号