Java synchronized 与 volatile
synchronized 提供互斥与可见性,volatile 提供可见性与有限有序性:何时用锁、何时用 volatile。
#concept
#growing
#dev
#java
[!info] related notes
Java synchronized 与 volatile
这个概念为什么出现
多线程共享可变状态时,不仅会“算错”,还会因缓存/重排序导致看见过期值。synchronized 提供临界区互斥与内存语义;volatile 让单次读写具备可见性与特定有序性,但不提供复合操作原子性。
[!abstract] 一句话理解
synchronized保证同一锁上互斥并建立 happens-before;volatile保证可见性与部分有序,不适合count++这类读改写。
最小可运行示例
场景:库存扣减必须互斥;开关标志用 volatile 发布
public class InventoryGuard {
private int stock;
private volatile boolean open = true; // 状态发布
InventoryGuard(int stock) { this.stock = stock; }
synchronized boolean reserve(int qty) {
if (qty <= 0) throw new IllegalArgumentException("qty must be positive");
if (!open) return false;
if (stock < qty) return false;
stock -= qty; // 读改写需要互斥
return true;
}
void shutdown() { open = false; } // 发布停止标志,不等待已经通过检查的 reserve
synchronized int stock() { return stock; }
public static void main(String[] args) throws Exception {
InventoryGuard g = new InventoryGuard(1);
Thread t1 = new Thread(() -> System.out.println("t1=" + g.reserve(1)));
Thread t2 = new Thread(() -> System.out.println("t2=" + g.reserve(1)));
t1.start(); t2.start();
t1.join(); t2.join();
System.out.println("left=" + g.stock());
}
}
建议运行:
javac InventoryGuard.java && java InventoryGuard
期望输出(顺序可互换,但成功预留恰好一次):
t1=true
t2=false
left=0
或 t1/t2 对调。
结合场景再看三个关注点
stock -= qty必须在同一把锁内。volatile适合旗标/发布,不适合复合更新。- 停止标志不等于关闭屏障:shutdown 返回后,已经读到 open=true 的 reserve 仍可能完成。若要求返回时无在途预留,shutdown 也应获取与 reserve 相同的锁。
核心概念与准确模型
1. synchronized
- 实例方法锁
this - static 方法锁
Class对象 - 同步块可指定锁对象
2. volatile
- 对同一 volatile 变量,写 happens-before 同步顺序中后续的读;还可发布该写之前的普通字段修改,但不把多个操作合成事务。\
- 不保证
i++原子 - 适合独立状态旗标、安全发布某些引用
3. 与 JMM
完整 happens-before 规则见 内存模型。
边界情况与反直觉行为
- 若同一共享状态有未同步的冲突访问,部分路径加锁不足以保证整体正确;需统一同步策略。\
- 锁顺序相反 → 死锁。
- 在锁内做远程 IO → 吞吐崩坏。
常见误区
[!warning] 常见误区:变量加了 volatile 就线程安全 复合操作仍需锁或原子类。
工程实践
- 优先高阶并发工具/不可变/分区。
- 锁内逻辑尽量短。
- 私有 final 锁对象避免外部抢锁。
- 用同步关系证明正确性,再以并发测试辅助找反例;OpenJDK jcstress 是并发测试工具,JCTools 主要提供并发数据结构。\
本节总结
- 互斥用锁;可见性旗标可用 volatile
- 读改写不是 volatile 的工作
- 为 JUC 与 JMM 深化做准备
自测题
- 为什么
volatile int c; c++不安全? - synchronized 方法与同步块差别?
参考答案
- c++ 是读-改-写三步,可交错。
- 方法锁固定为 this/Class;同步块可更细粒度选锁与范围。
延伸阅读与资料来源
| 资料 | 类型 | 支撑内容 |
|---|---|---|
| Synchronization (Tutorial) | 教程 | synchronized |
| JLS volatile | 规范 | volatile 语义 |