1. LockSupport
特点:
-
最底层的线程阻塞/唤醒工具
-
不依赖锁对象,直接操作线程
-
提供许可证(permit)机制(类似信号量,但最多只有1个)
-
不可重入
核心方法:
-
park()
- 阻塞当前线程 -
unpark(Thread thread)
- 唤醒指定线程 -
parkNanos(long nanos)
- 带超时的阻塞
优势:
-
比Object.wait()/notify()更灵活
-
不会导致死锁(因为没有锁的获取顺序问题)
-
可先unpark后park(许可证机制)
使用场景:
-
构建高级同步工具的基础
-
需要精确控制线程阻塞/唤醒的场景
-
AQS(AbstractQueuedSynchronizer)的实现基础
2. ReentrantLock
特点:
-
可重入互斥锁
-
提供公平锁和非公平锁两种模式
-
支持尝试获取锁(tryLock)
-
支持可中断的锁获取
-
支持条件变量(Condition)
核心方法:
-
lock()
,unlock()
-
tryLock()
,tryLock(long timeout, TimeUnit unit)
-
lockInterruptibly()
-
newCondition()
优势:
-
比synchronized更灵活
-
可中断的锁获取
-
支持多个条件变量
-
可设置公平性
使用场景:
-
需要可中断锁获取的场景
-
需要尝试获取锁的场景
-
需要多个条件变量的场景
-
需要公平锁的场景
3. ReentrantReadWriteLock
特点:
-
读写锁分离:读锁共享,写锁独占
-
可重入
-
支持公平和非公平模式
-
支持锁降级(写锁降级为读锁)
-
不支持锁升级(读锁升级为写锁)
核心结构:
-
ReadLock
- 共享读锁 -
WriteLock
- 独占写锁
优势:
-
读多写少场景性能优于互斥锁
-
减少读操作间的竞争
使用场景:
-
读操作远多于写操作的场景
-
需要保证写操作的可见性
-
缓存系统实现
注意事项:
-
写锁饥饿问题(公平模式下可缓解)
-
不适合写多读少的场景
4. StampedLock
特点:
-
JDK8新增的锁机制
-
三种访问模式:
-
写锁(独占,类似ReentrantLock)
-
悲观读锁(共享,类似ReentrantReadWriteLock的读锁)
-
乐观读(无锁,类似volatile读)
-
-
不可重入
-
不支持条件变量
-
提供锁转换功能
核心方法:
-
writeLock()
,tryWriteLock()
-
readLock()
,tryReadLock()
-
tryOptimisticRead()
-
validate(long stamp)
- 验证乐观读期间是否有写操作
优势:
-
乐观读模式提供更高的并发度
-
比ReentrantReadWriteLock更高的吞吐量
-
支持锁转换(如读锁转写锁)
使用场景:
-
读多写少且对数据一致性要求不严格的场景
-
需要极高读性能的场景
-
适合简单的数据模型
注意事项:
-
使用较复杂,容易出错
-
乐观读后需要验证
-
不支持重入和条件变量
对比总结
特性 | LockSupport | ReentrantLock | ReentrantReadWriteLock | StampedLock |
---|---|---|---|---|
锁类型 | 线程阻塞工具 | 互斥锁 | 读写分离锁 | 多模式锁 |
重入性 | 不支持 | 支持 | 支持 | 不支持 |
公平性 | 无 | 可配置 | 可配置 | 不可配置 |
条件变量 | 无 | 支持 | 支持 | 不支持 |
读写分离 | 无 | 无 | 读锁/写锁 | 读锁/写锁/乐观读 |
性能 | 高 | 中 | 读多写少场景高 | 极高(乐观读模式) |
复杂性 | 低 | 中 | 中 | 高 |
适用场景 | 构建同步工具基础 | 通用互斥需求 | 读多写少 | 极高读性能需求 |
选择建议
-
构建自定义同步器:LockSupport
-
通用互斥需求:ReentrantLock
-
典型读多写少:ReentrantReadWriteLock
-
极高读性能需求:StampedLock
-
需要条件变量:ReentrantLock或ReentrantReadWriteLock
-
需要乐观读:StampedLock
注意事项
-
锁的粒度:尽量减小锁的持有时间和范围
-
死锁预防:避免嵌套锁,按固定顺序获取锁
-
性能考量:在高竞争环境下测试不同锁的性能
-
StampedLock陷阱:乐观读后必须验证,且容易误用
-
读写锁适用性:评估读写比例,不适用于写多场景
代码示例
StampedLock乐观读示例
class Point {
private double x, y;
private final StampedLock sl = new StampedLock();
void move(double deltaX, double deltaY) {
long stamp = sl.writeLock();
try {
x += deltaX;
y += deltaY;
} finally {
sl.unlockWrite(stamp);
}
}
double distanceFromOrigin() {
long stamp = sl.tryOptimisticRead(); // 乐观读
double currentX = x, currentY = y;
if (!sl.validate(stamp)) { // 检查是否有写操作
stamp = sl.readLock(); // 获取悲观读锁
try {
currentX = x;
currentY = y;
} finally {
sl.unlockRead(stamp);
}
}
return Math.sqrt(currentX * currentX + currentY * currentY);
}
}