java常见的锁
概念一:synchronized(内置锁)
一、是什么?synchronized
是 Java 内置的互斥锁,基于 JVM 的监视器(Monitor)机制实现。可修饰方法或代码块,保证同一时刻仅一个线程能访问被保护的资源。
二、解决什么问题
解决多线程环境下的共享资源竞争问题,避免数据不一致和竞态条件(Race Condition)。
三、核心方法
- 修饰实例方法:锁定当前对象实例
- 修饰静态方法:锁定当前类的 Class 对象
- 同步代码块:手动指定锁对象(如
synchronized(obj)
)
四、应用场景
- 简单的线程同步(如单例模式的双重检查锁)
- 保护共享变量的原子操作
- 不需要复杂锁控制的场景(如计数器累加)
五、Java示例
java
public class Counter {
private int count = 0;
// 同步方法
public synchronized void increment() {
count++;
}
// 同步代码块
public void add() {
synchronized(this) {
count += 2;
}
}
}
六、重要注意事项
- 自动释放锁:代码块结束或方法返回时自动释放
- 可重入性:同一线程可重复获取同一把锁
- 性能优化:JDK6+ 引入锁升级机制(无锁 → 偏向锁 → 轻量级锁 → 重量级锁)
- 无法中断:等待锁的线程不能被中断
- 非公平锁:默认不保证等待线程的获取顺序
概念二:ReentrantLock(显式锁)
一、是什么?java.util.concurrent.locks.ReentrantLock
是 JDK 提供的显式锁,实现了 Lock
接口,提供比 synchronized
更灵活的锁控制。
二、解决什么问题
synchronized
的局限性:无法中断等待、无法超时获取、不支持公平锁- 需要更细粒度的锁控制(如条件变量)
三、核心方法
lock()
:获取锁(阻塞)unlock()
:释放锁(需在 finally 中调用)tryLock()
:尝试非阻塞获取锁lockInterruptibly()
:可中断获取锁newCondition()
:创建条件变量
四、应用场景
- 需要尝试获取锁或超时控制的场景
- 公平锁需求(按等待顺序获取锁)
- 复杂同步逻辑(如生产者-消费者模型)
- 需要锁统计信息(如获取锁的次数)
五、Java示例
java
import java.util.concurrent.locks.ReentrantLock;
public class SafeCounter {
private final ReentrantLock lock = new ReentrantLock();
private int value = 0;
public void increment() {
lock.lock(); // 手动加锁
try {
value++;
} finally {
lock.unlock(); // 必须手动释放
}
}
// 尝试获取锁(超时2秒)
public boolean tryIncrement() throws InterruptedException {
if (lock.tryLock(2, TimeUnit.SECONDS)) {
try {
value++;
return true;
} finally {
lock.unlock();
}
}
return false;
}
}
六、重要注意事项
- 必须显式释放锁:否则会导致死锁
- 可重入性:支持同一线程重复加锁
- 公平性选择:构造函数可指定公平/非公平模式(默认非公平)
- 条件变量:通过
Condition
实现精确的线程唤醒 - 锁中断:支持响应线程中断请求
概念三:ReadWriteLock(读写锁)
一、是什么?java.util.concurrent.locks.ReadWriteLock
是接口,其实现类 ReentrantReadWriteLock
将锁分为:
- 读锁(共享锁):允许多线程并发读
- 写锁(排他锁):仅允许单线程写
二、解决什么问题
解决读多写少场景的性能瓶颈:
synchronized
和ReentrantLock
在纯读操作时仍互斥- 读写锁允许多线程同时读,提高并发性能
三、核心方法
readLock()
:获取读锁writeLock()
:获取写锁- 支持锁降级(写锁 → 读锁),不支持锁升级
四、应用场景
- 缓存系统(如 Redis 的读写分离)
- 高频查询低频修改的数据结构(如配置中心)
- 需要保证写操作的原子性,同时允许并发读
五、Java示例
java
import java.util.concurrent.locks.ReentrantReadWriteLock;
public class CachedData {
private Object data;
private final ReentrantReadWriteLock rwLock = new ReentrantReadWriteLock();
public void updateData(Object newData) {
rwLock.writeLock().lock(); // 获取写锁
try {
data = newData; // 写操作(独占)
} finally {
rwLock.writeLock().unlock();
}
}
public Object readData() {
rwLock.readLock().lock(); // 获取读锁
try {
return data; // 读操作(共享)
} finally {
rwLock.readLock().unlock();
}
}
}
六、重要注意事项
- 写锁优先级:默认非公平模式下,写锁可插队(避免读锁饥饿)
- 锁降级:持有写锁时可获取读锁 → 释放写锁 → 保留读锁(保证数据一致性)
- 不支持锁升级:读锁 → 写锁会导致死锁
- 重入性:读锁和写锁均可重入
概念四:StampedLock(邮戳锁 - JDK8+)
一、是什么?java.util.concurrent.locks.StampedLock
是 JDK8 引入的优化读写锁,支持三种模式:
- 写锁:独占锁
- 悲观读锁:类似
ReadWriteLock
的读锁 - 乐观读:无锁读取,通过邮戳(Stamp)验证数据一致性
二、解决什么问题
ReadWriteLock
在读多写少时仍存在性能开销- 提供无锁读机制(乐观读),大幅提升读性能
- 解决读写锁的线程饥饿问题
三、核心方法
writeLock()
:获取写锁,返回邮戳readLock()
:获取悲观读锁,返回邮戳tryOptimisticRead()
:乐观读(无锁),返回邮戳validate(stamp)
:验证乐观读期间是否有写操作tryConvertToWriteLock(stamp)
:尝试升级为写锁
四、应用场景
- 极高并发读场景(如金融行情系统)
- 读操作远多于写操作(≥ 100:1)
- 需要极低延迟的读操作
五、Java示例
java
import java.util.concurrent.locks.StampedLock;
public class Point {
private double x, y;
private final StampedLock sl = new StampedLock();
// 乐观读
public double distanceFromOrigin() {
long stamp = sl.tryOptimisticRead(); // 无锁读取
double curX = x, curY = y;
if (!sl.validate(stamp)) { // 检查期间是否有写操作
stamp = sl.readLock(); // 转为悲观读
try {
curX = x;
curY = y;
} finally {
sl.unlockRead(stamp);
}
}
return Math.sqrt(curX * curX + curY * curY);
}
// 写操作
public void move(double deltaX, double deltaY) {
long stamp = sl.writeLock();
try {
x += deltaX;
y += deltaY;
} finally {
sl.unlockWrite(stamp);
}
}
}
六、重要注意事项
- 不可重入:同一线程重复获取锁会死锁
- 无条件变量:不支持
Condition
- 乐观读风险:需通过
validate()
严格校验数据一致性 - 锁转换:支持悲观读锁 → 写锁升级(需循环尝试)
- 适用场景:仅推荐在读操作占绝对优势时使用
概念五:其他锁(简要介绍)
除了上述常见的锁,Java中还有一些其他锁机制,如:
- Condition:通常与ReentrantLock配合使用,用于实现线程间的条件等待(类似Object.wait()/notify())。
- LockSupport:提供更底层的线程阻塞和唤醒操作(park/unpark)。
- Semaphore:信号量,用于控制同时访问特定资源的线程数量。
- CountDownLatch、CyclicBarrier:用于线程间的同步协作。
锁的核心区别对比
特性 | synchronized | ReentrantLock | ReadWriteLock | StampedLock |
---|---|---|---|---|
锁类型 | 内置锁 | 显式锁 | 读写分离锁 | 优化读写锁 |
获取方式 | JVM 自动管理 | 手动 lock/unlock | 手动获取读/写锁 | 手动管理邮戳 |
公平锁 | 不支持 | 支持(构造函数) | 支持(构造函数) | 不支持 |
可重入性 | 支持 | 支持 | 支持 | 不支持 |
条件变量 | wait/notify | 支持 Condition | 支持 Condition | 不支持 |
锁中断 | 不支持 | 支持 lockInterruptibly | 支持 | 仅写锁支持 |
超时获取 | 不支持 | 支持 tryLock | 支持 tryLock | 支持 tryWriteLock |
性能(读多写少) | 低 | 中 | 高 | 极高(乐观读) |
JDK 版本 | 所有版本 | JDK5+ | JDK5+ | JDK8+ |
总结
- synchronized:首选简单场景,自动管理锁,性能已优化
- ReentrantLock:需高级功能时使用(如公平锁、条件变量)
- ReadWriteLock:读多写少场景的标准解决方案
- StampedLock:JDK8+ 的极致读性能选择(但需谨慎使用)
锁选择建议:
- 优先用
synchronized
(90% 场景够用)- 需要灵活控制时选
ReentrantLock
- 读操作占比 ≥ 80% 时用
ReadWriteLock
- 极端读密集型(≥ 100:1)且 JDK8+ 时考虑
StampedLock