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》并发条目(锁对象选择、线程池)