synchronized 与 ReentrantLock 对比
synchronized
和 ReentrantLock
是 Java 提供的两种实现线程同步的方式,它们都能够解决多线程访问共享资源时的数据一致性问题。虽然功能相似,但它们在实现机制、性能和灵活性方面存在显著差异。
1. 基本介绍
synchronized
- 类型:Java 的关键字,内置锁。
- 特性:简单易用,代码结构清晰。在方法或代码块上加锁,自动管理锁的获取和释放。JVM 提供了性能优化(偏向锁、轻量级锁等)。
ReentrantLock
- 类型:显式锁(
java.util.concurrent.locks
包)。 - 特性:提供更灵活的锁功能。手动控制锁的获取与释放,支持条件变量、可中断锁和超时机制。需要正确使用 lock() 和 unlock(),否则可能导致死锁。
2. 区别对比
特性 | synchronized | ReentrantLock |
实现类型 | JVM 内置,依赖 | 显式锁,基于 |
锁的获取和释放 | 自动获取和释放,线程退出同步块后自动释放锁 | 手动获取( |
可中断性 | 不支持 | 支持可中断锁,通过 |
公平性 | 不支持 | 支持公平锁和非公平锁(默认非公平),通过构造方法设置 |
性能 | JDK 1.6 后性能接近,但锁粒度较粗 | 性能高,灵活性强,适合高并发场景 |
锁重入性 | 支持(同一线程可以多次获取锁) | 支持(同一线程可以多次获取锁) |
锁超时 | 不支持 | 支持,通过 |
条件变量支持 | 不支持 | 支持条件变量( |
锁升级/优化机制 | 偏向锁、轻量级锁、重量级锁动态升级 | 无锁优化机制,直接基于 AQS 实现 |
适用场景 | 简单场景,代码结构清晰,性能优化后适合大多数场景 | 高并发场景,复杂线程协调需求 |
3. 具体功能对比
(1) 可中断性
- synchronized:当一个线程在等待
synchronized
锁时,如果被阻塞,无法被中断。 - ReentrantLock:可以通过
lockInterruptibly()
方法实现可中断的锁机制,在等待锁时可以响应中断信号。
示例:
lock.lockInterruptibly(); // 可响应中断信号
(2) 公平性
- synchronized:无法保证公平性,线程获取锁的顺序不一定按请求顺序。
- ReentrantLock:支持公平锁和非公平锁(默认非公平锁)。公平锁通过构造函数设置:
(3) 锁超时
- synchronized:线程无法设置超时时间,只能等待锁释放。
- ReentrantLock:支持超时机制,通过
tryLock(long timeout, TimeUnit unit)
实现。
示例:
if (lock.tryLock(1, TimeUnit.SECONDS)) { try { // 获得锁后的操作 } finally { lock.unlock(); } } else { // 超时未获取锁的处理 }
(4) 条件变量
- synchronized:不支持条件变量,线程只能通过
wait()
和notify()
进行协调。 - ReentrantLock:支持多个条件变量,通过
Condition
实现更复杂的线程协作。
示例:
Condition condition = lock.newCondition(); lock.lock(); try { condition.await(); // 等待信号 condition.signal(); // 发出信号 } finally { lock.unlock(); }
(5) 性能
- synchronized:JDK 1.6 后性能大幅优化,支持偏向锁、轻量级锁等,适合大多数场景。
- ReentrantLock:性能高,更适合高并发和需要额外功能(如超时、条件变量)的复杂场景。
4. 使用示例
(1) 使用 synchronized
public class SynchronizedExample { private int counter = 0; public synchronized void increment() { counter++; } public synchronized int getCounter() { return counter; } }
(2) 使用 ReentrantLock
import java.util.concurrent.locks.ReentrantLock; public class ReentrantLockExample { private final ReentrantLock lock = new ReentrantLock(); private int counter = 0; public void increment() { lock.lock(); // 获取锁 try { counter++; } finally { lock.unlock(); // 确保锁被释放 } } public int getCounter() { lock.lock(); try { return counter; } finally { lock.unlock(); } } }
(3) 使用条件变量
import java.util.concurrent.locks.Condition; import java.util.concurrent.locks.ReentrantLock; public class ConditionExample { private final ReentrantLock lock = new ReentrantLock(); private final Condition condition = lock.newCondition(); public void awaitSignal() throws InterruptedException { lock.lock(); try { System.out.println("Waiting for signal..."); condition.await(); // 等待信号 System.out.println("Signal received!"); } finally { lock.unlock(); } } public void sendSignal() { lock.lock(); try { System.out.println("Sending signal..."); condition.signal(); // 发出信号 } finally { lock.unlock(); } } }
5. 选择建议
- 选择 synchronized 的场景:同步逻辑简单,代码可读性更重要。不需要锁的高级功能(如条件变量、可中断锁、超时锁)。小型项目或低并发场景。
- 选择 ReentrantLock 的场景:高并发场景,需更高的性能和灵活性。需要公平锁、可中断锁、超时锁等高级功能。需要使用条件变量实现复杂的线程协作。
Java碎碎念 文章被收录于专栏
来一杯咖啡,聊聊Java的碎碎念呀