应根据需求选择锁机制:简单场景用synchronized;需中断、超时或公平策略用reentrantlock;读多写少且可容忍短暂不一致才用stampedlock。

synchronized 关键字:最直接但容易误用
Java 里最基础的加锁方式,编译器自动处理锁的获取和释放,适合简单同步场景。但它锁的是对象监视器(monitor),不是代码块本身,这点常被忽略。
常见错误现象:NullPointerException(锁对象为 null)、死锁(多个 synchronized 方法交叉调用且锁顺序不一致)、锁粒度太大导致吞吐量骤降。
- 实例方法上加
synchronized,锁的是当前实例(this);静态方法上加,锁的是类对象(MyClass.class) - 同步代码块推荐写成
synchronized(obj) { ... },且obj必须是不可变、非空、生命周期可控的对象(避免用String或装箱类型如Integer作锁) - 不能中断等待锁的线程,也不能超时尝试,一旦阻塞就只能等
ReentrantLock:可中断、可超时、可公平,但必须手动释放
ReentrantLock 提供了比 synchronized 更细粒度的控制能力,但代价是必须显式调用 lock() 和 unlock(),漏掉 unlock() 就会造成永久阻塞。
使用场景:需要响应中断(比如线程被 interrupt() 时及时退出)、需要尝试获取锁(tryLock())、或需指定公平策略(new ReentrantLock(true))。
立即学习“Java免费学习笔记(深入)”;
- 务必在
finally块中调用unlock(),否则极易发生“锁泄漏” -
lockInterruptibly()可被中断,而lock()不会响应中断信号 - 支持条件队列(
Condition),比wait()/notify()更灵活,但一个ReentrantLock可绑定多个Condition
StampedLock:读多写少场景下的高性能替代,但不支持重入
StampedLock 是 JDK 8 引入的乐观读锁机制,适用于读操作远多于写操作、且对写一致性要求不极端严格的场景。它不基于 monitor,也不支持重入,用法和前两者完全不同。
容易踩的坑:把 tryOptimisticRead() 返回的 stamp 当作“锁成功”,其实它只是个版本戳;未校验 stamp 是否有效就使用数据,会导致读到脏值。
- 乐观读失败后要退回到悲观读(
readLock()),或写锁(writeLock()) - 每次读取共享变量前,必须调用
validate(stamp)判断是否被其他线程修改过 - 不支持条件等待,不能替代
wait/notify或Condition - 写锁是独占的,但读锁之间不互斥,也和乐观读不互斥——这是性能优势来源,也是理解难点
选哪个?关键看三件事
不是越新越好,也不是越灵活越合适。决定因素其实是:是否需要中断、是否允许脏读、是否频繁写入。
- 只保护几行简单逻辑,无中断/超时需求 → 用
synchronized,够用且不易出错 - 需要
tryLock(timeout)、要响应中断、或需多个等待队列 → 上ReentrantLock,但得盯紧finally里的unlock() - 读操作占比 >95%,能容忍极短时间内的不一致(比如缓存刷新延迟),且确定不会递归调用读逻辑 → 才考虑
StampedLock,否则反而增加复杂度和风险
最容易被忽略的一点:锁对象的作用域。无论哪种方式,锁对象如果被外部修改或复用,都会让同步失效。别让锁变成“形同虚设”的全局变量。










