一、锁的底层实现原理
1. 悲观锁的实现细节
-
synchronized 关键字:
- 偏向锁:当锁对象第一次被线程获取时,线程在对象头中记录偏向线程 ID,后续该线程再次获取锁无需 CAS 操作。
- 轻量级锁:当有其他线程尝试竞争偏向锁时,偏向锁升级为轻量级锁。线程在栈帧中创建锁记录(Lock Record),通过 CAS 将对象头的 Mark Word 复制到锁记录中。
- 重量级锁:若多个线程交替执行同步块,轻量级锁效率高;但若存在锁竞争,轻量级锁会膨胀为重量级锁,依赖操作系统的互斥量(Mutex),导致线程阻塞和用户态 / 内核态切换。
-
ReentrantLock:
- 基于 AQS(AbstractQueuedSynchronizer)实现,通过 CAS 设置 state 变量表示锁的获取状态。
- 公平锁:线程按请求顺序获取锁,通过 FIFO 队列实现。
- 非公平锁:允许线程在队列中插队,减少线程切换开销,但可能导致某些线程饥饿。
2. 乐观锁的实现细节
-
CAS 的硬件支持:
- CAS 操作依赖处理器的
cmpxchg
指令(x86 架构),确保原子性。 - Java 中的
Unsafe
类通过 JNI 调用底层 CAS 指令。
- CAS 操作依赖处理器的
-
原子类的分层设计:
// AtomicInteger的incrementAndGet()方法源码 public final int incrementAndGet() { return U.getAndAddInt(this, VALUE, 1) + 1; } // Unsafe类的getAndAddInt方法 public final int getAndAddInt(Object o, long offset, int delta) { int v; do { v = getIntVolatile(o, offset); } while (!weakCompareAndSetInt(o, offset, v, v + delta)); return v; }
weakCompareAndSetInt
与compareAndSetInt
的区别:前者允许虚假失败(spurious failure),但在 JDK 实现中二者通常等价。
二、锁的性能对比与调优
1. 性能基准测试
- 测试工具:JMH(Java Microbenchmark Harness)
- 测试场景:
- 高并发写:悲观锁(ReentrantLock)吞吐量约为 10 万 TPS,乐观锁(AtomicInteger)约为 50 万 TPS。
- 低冲突写:悲观锁因线程阻塞导致上下文切换开销,乐观锁优势明显。
- 高冲突写:乐观锁 CAS 重试频繁,吞吐量下降至约 20 万 TPS,此时悲观锁更稳定。
2. 锁的优化策略
-
锁粗化(Lock Coarsening):
// 优化前 public void method() { synchronized(this) { // 操作1 } synchronized(this) { // 操作2 } } // 优化后(JVM自动优化) public void method() { synchronized(this) { // 操作1 // 操作2 } }
-
锁消除(Lock Elimination):
public void method() { StringBuffer sb = new StringBuffer(); sb.append("a"); // StringBuffer的append是synchronized方法,但sb是局部变量,JVM会消除锁 }
-
分段锁(Striped Lock):
// ConcurrentHashMap的分段锁设计(JDK7及以前) static final int DEFAULT_CONCURRENCY_LEVEL = 16; Segment<K,V>[] segments;
三、高级应用场景
1. 乐观锁在分布式系统中的应用
-
数据库乐观锁:
UPDATE table SET column = value, version = version + 1 WHERE id = ? AND version = ?;
-
分布式锁:
// 使用Redis实现分布式乐观锁 public boolean updateWithOptimisticLock(String key, String value, String expectedVersion) { String script = "if redis.call('get', KEYS[1]) == ARGV[1] " + "then redis.call('set', KEYS[1], ARGV[2]); return 1 " + "else return 0 end"; return redisTemplate.execute(script, Collections.singletonList(key), expectedVersion, value) == 1; }
2. 悲观锁的扩展应用
- 可重入锁的嵌套调用:
public class ReentrantExample { private final ReentrantLock lock = new ReentrantLock(); public void outer() { lock.lock(); try { System.out.println("Outer locked"); inner(); } finally { lock.unlock(); } } public void inner() { lock.lock(); // 可重入,无需等待 try { System.out.println("Inner locked"); } finally { lock.unlock(); } } }
四、CAS 的深层问题与解决方案
1. ABA 问题的进阶解决方案
- AtomicMarkableReference:
private AtomicMarkableReference<Integer> balance = new AtomicMarkableReference<>(100, false); public void transfer() { boolean[] markHolder = new boolean[1]; int oldBalance = balance.get(markHolder); boolean oldMark = markHolder[0]; // 操作... balance.compareAndSet(oldBalance, newBalance, oldMark, !oldMark); }
2. 原子类的局限性
- LongAdder vs AtomicLong:
// JDK8引入的LongAdder,在高并发下性能优于AtomicLong private LongAdder counter = new LongAdder(); public void increment() { counter.increment(); // 分段累加,减少CAS冲突 } public long sum() { return counter.sum(); // 汇总各分段结果 }
五、锁的选择决策树
是否需要跨进程/节点的锁?
├─ 是 → 使用分布式锁(Redis、ZooKeeper)
└─ 否 → 是否保证强一致性?
├─ 是 → 冲突概率高?
│ ├─ 是 → 使用悲观锁(synchronized/ReentrantLock)
│ └─ 否 → 使用乐观锁(Atomic类 + 重试)
└─ 否 → 使用无锁数据结构(ConcurrentHashMap)
六、JDK 9 + 的新特性
1. VarHandle
import java.lang.invoke.MethodHandles;
import java.lang.invoke.VarHandle;
public class VarHandleExample {
private static final VarHandle VALUE;
private int value;
static {
try {
VALUE = MethodHandles.lookup().findVarHandle(
VarHandleExample.class, "value", int.class);
} catch (NoSuchFieldException | IllegalAccessException e) {
throw new Error(e);
}
}
public void increment() {
VALUE.getAndAdd(this, 1); // 等价于AtomicInteger的操作
}
}
2. 增强的 StampedLock
public class Point {
private double x, y;
private final StampedLock sl = new StampedLock();
public void move(double deltaX, double deltaY) {
long stamp = sl.writeLock(); // 获取写锁
try {
x += deltaX;
y += deltaY;
} finally {
sl.unlockWrite(stamp);
}
}
public 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.hypot(currentX, currentY);
}
}
总结
选择锁的核心原则:
- 读多写少:优先使用乐观锁,配合
Atomic
类或StampedLock
。 - 写多读少:优先使用悲观锁,配合
ReentrantLock
或synchronized
。 - 高并发场景:考虑分段锁(如
ConcurrentHashMap
)或LongAdder
。 - 分布式系统:使用数据库版本号或 Redis 实现乐观锁,或基于 ZooKeeper 实现分布式悲观锁。
现代 JDK 中,锁机制不断演进,从偏向锁到VarHandle
,性能和灵活性持续提升。开发者需根据具体场景选择合适的锁策略,并关注 JDK 新特性以优化并发性能。