在并发编程中,锁机制是保证线程安全的重要手段。Java 提供了多种锁机制,从最基础的 synchronized
到功能更强大的 ReentrantLock
,再到高性能的 StampedLock
。本文将对三者进行系统对比,帮助你在实际开发中做出合理选择。
1. Synchronized
特点
- Java 关键字,语法层面支持。
- 编译器和 JVM 层面优化(锁升级:偏向锁 → 轻量级锁 → 重量级锁)。
- 自动释放锁,无需手动 unlock。
- 不能中断等待锁的线程。
- 公平性不可控,默认非公平。
使用示例
java
public synchronized void increment() {
count++;
}
public void test() {
synchronized (this) {
count++;
}
}
2. ReentrantLock
特点
- 位于
java.util.concurrent.locks
包下。 - 可重入,支持公平锁 / 非公平锁选择。
- 提供
tryLock()
(可超时获取锁)、lockInterruptibly()
(可中断获取锁)。 - 需要手动释放锁,必须在
finally
中调用unlock()
。 - 提供
Condition
实现精确的线程通信。
使用示例
csharp
private final ReentrantLock lock = new ReentrantLock();
public void increment() {
lock.lock();
try {
count++;
} finally {
lock.unlock();
}
}
带超时:
csharp
if (lock.tryLock(1, TimeUnit.SECONDS)) {
try {
// do work
} finally {
lock.unlock();
}
}
3. StampedLock
特点
-
Java 8 引入,主要用于优化读多写少的场景。
-
提供三种模式:
- 写锁(writeLock) :独占。
- 读锁(readLock) :共享。
- 乐观读(tryOptimisticRead) :无锁,提升并发性能。
-
不可重入,不支持条件变量。
-
需要手动释放锁。
使用示例
ini
private final StampedLock lock = new StampedLock();
private double x, y;
void move(double deltaX, double deltaY) {
long stamp = lock.writeLock();
try {
x += deltaX;
y += deltaY;
} finally {
lock.unlockWrite(stamp);
}
}
double distanceFromOrigin() {
long stamp = lock.tryOptimisticRead();
double currentX = x, currentY = y;
if (!lock.validate(stamp)) {
stamp = lock.readLock();
try {
currentX = x;
currentY = y;
} finally {
lock.unlockRead(stamp);
}
}
return Math.sqrt(currentX * currentX + currentY * currentY);
}
4. 三者对比
特性 | synchronized | ReentrantLock | StampedLock |
---|---|---|---|
可重入 | ✅ | ✅ | ❌ |
公平锁支持 | ❌ | ✅ | ❌ |
自动释放锁 | ✅ | ❌ | ❌ |
可中断 | ❌ | ✅ | ❌ |
条件变量 | ❌ | ✅ | ❌ |
乐观读支持 | ❌ | ❌ | ✅ |
性能优化 | JVM 优化 | AQS 实现 | 读多写少场景 |
5. 适用场景
- synchronized:简单同步逻辑,代码可读性要求高的场景。
- ReentrantLock:需要可中断、公平性、条件变量的复杂同步场景。
- StampedLock:高并发、读多写少的性能敏感场景。
6. 总结
- 如果只是简单的锁需求,
synchronized
足够,且 JVM 已经做了大量优化。 - 如果需要灵活控制锁,
ReentrantLock
是首选。 - 如果是读多写少的业务,推荐使用
StampedLock
,能够显著提升并发性能。