跳转到内容

synchronized 与锁升级

用途:理解内置锁的开销来源、判断该用内置锁还是显式锁

synchronized 同时提供三件事:互斥、可见性(解锁前的写对后续加锁者可见)、有序性(临界区内不重排到锁外)。

可作用位置:实例方法(锁 this)、静态方法(锁 Class 对象)、代码块(显式锁对象)。不要用字符串常量、Integer 装箱对象、this 逃逸给外部当锁——会造成意外共享或粒度失控。锁对象应是私有 final 字段。

锁的对象可以是任何引用,因此可以把细碎竞争合并到一把锁上(分段锁的反面)。常见做法:用专用锁对象而非业务对象本身,便于注释说明保护哪些字段。

偏向锁 → 轻量级锁 → 重量级锁

「偏向锁 → 轻量级锁 → 重量级锁」小节链接

HotSpot 的内置锁按竞争程度逐步升级(不同版本策略有差异,新版 JDK 已弱化或移除偏向锁,以当前版本官方说明为准):

阶段 适用 机制 代价
偏向锁 始终同一线程进入 Mark Word 记录线程 ID,重入无需 CAS 撤销偏向需要 safepoint,故新 JDK 倾向取消
轻量级锁 少量交替竞争 栈内锁记录 + CAS 自旋消耗 CPU
重量级锁 真实并发竞争 依赖 OS 互斥量,线程阻塞/唤醒 上下文切换开销

工程含义:不要为了“避免锁升级”设计奇怪结构,而要减少临界区大小与竞争概率(分段、无锁结构、减少共享)。

wait/notify/notifyAll 必须在持有同一对象锁时调用;wait 必须放在循环条件判断里(防虚假唤醒与条件被其他线程抢先改变)。notify 与 notifyAll 的选择:等待者条件互不相同用 notifyAll,或用 Condition 分组唤醒(见 AQS 词条)。

场景 选择
简单互斥、临界区短 synchronized(代码简洁,异常自动释放)
需要超时、可中断、公平锁、多条件队列 ReentrantLock
读多写少 ReentrantReadWriteLock 或 StampedLock(乐观读)
高竞争计数 原子类或 LongAdder,而不是锁
误区 修正
锁方法但临界区包含 RPC/IO 把远程调用移出锁,或改异步
if (flag == false) wait() 用 while 判断条件
用 Integer/String 当锁 私有 final Object 锁
认为 synchronized 已过时 无竞争路径开销很低,仍是首选简单方案
到处加锁“以防万一” 明确每个锁保护哪些字段,写在注释里
  • 《Java 并发编程实战》第 9、10、14 章
  • OpenJDK 关于 Object Monitors 的官方文档(HotSpot 同步实现)
  • Josh Bloch《Effective Java》并发条目(锁对象选择、线程池)