Java 各种锁对比
Java 提供了多种锁机制,每种锁都有其特点和适用场景。以下是主要锁类型的对比:
1. 乐观锁 vs 悲观锁
| 特性 | 乐观锁 | 悲观锁 |
|---|
| 实现方式 | 版本号/CAS | synchronized/ReentrantLock |
| 适用场景 | 读多写少 | 写多读少 |
| 并发性能 | 高 | 低 |
| 冲突处理 | 重试或报错 | 阻塞等待 |
| 示例 | AtomicInteger, StampedLock | synchronized |
2. synchronized 锁
- 类型:悲观锁、可重入锁、非公平锁
- 特点:
- JVM 内置实现,自动加锁/释放锁
- 在 JDK 1.6 后进行了大量优化(偏向锁、轻量级锁、重量级锁)
- 不可中断
- 非公平锁
3. ReentrantLock
| 特性 | ReentrantLock | synchronized |
|---|
| 实现方式 | API 层面 | JVM 层面 |
| 锁获取 | 可尝试获取(tryLock)、可定时、可中断 | 不可 |
| 公平性 | 可选择公平或非公平(默认非公平) | 非公平 |
| 条件变量 | 支持多个 Condition | 只支持一个 wait/notify |
| 性能 | JDK 1.6 后两者性能接近 | JDK 1.6 后优化良好 |
| 锁释放 | 必须手动 unlock() | 自动释放 |
4. ReentrantReadWriteLock
- 读写分离锁,读锁共享,写锁独占
- 特点:
- 读读不互斥
- 读写、写写互斥
- 写锁可降级为读锁,但读锁不能升级为写锁
- 可能导致写线程饥饿
5. StampedLock
- JDK 1.8 新增,性能优于 ReentrantReadWriteLock
- 三种模式:
- 写锁:独占,类似 ReentrantLock
- 悲观读锁:类似 ReentrantReadWriteLock 的读锁
- 乐观读:不加锁,通过 validate() 方法验证
- 特点:
- 不可重入
- 不支持 Condition
- 乐观读适用于读多写少场景
6. 分布式锁
| 实现方式 | 特点 |
|---|
| 数据库实现 | 简单但性能差,可用基于唯一键或版本号实现 |
| Redis 实现 | 高性能,常用 SETNX 或 RedLock 算法,需考虑锁续期问题 |
| Zookeeper 实现 | 可靠性高,基于临时顺序节点实现,有羊群效应问题 |
7. 锁优化建议
- 减少锁持有时间(同步代码块尽量小)
- 减小锁粒度(如 ConcurrentHashMap 分段锁)
- 读写分离(使用 ReadWriteLock)
- 无锁编程(CAS 操作)
- 锁粗化(合并连续的小锁请求)
- 避免死锁(按固定顺序获取锁)
选择锁的考量因素
- 并发度(高并发考虑更轻量级的锁)
- 读写比例(读多写少考虑读写锁)
- 是否需要公平性
- 是否需要可中断、超时功能
- 是否需要条件变量
根据具体场景选择合适的锁机制,才能达到最佳的性能和安全性平衡。