Java内存模型(JMM)与并发基础
开篇:当咖啡师遇上多线程
想象一家网红咖啡店,三位咖啡师(线程)同时操作一台意式咖啡机(共享资源)。没有明确分工时可能出现:A师傅刚磨好粉,B师傅就清空了粉碗,C师傅看着空粉碗发愣。这就是并发编程的日常——JMM就是帮助咖啡师们高效协作的《吧台操作手册》。
第一章 看得见的混乱:并发问题三原罪
1.1 原子性问题:被打断的拉花教学
新手咖啡师小李正在学习心形拉花:
// 理想中的步骤
1. 倾斜咖啡杯 → 2. 注入奶泡
3. 左右晃动 → 4. 垂直收尾
实际可能被订单打断:
// 线程切换导致步骤错乱
1. 倾斜咖啡杯 → [接单提醒弹出]
→ 3. 左右晃动
2. 注入奶泡 → 4. 垂直收尾
// 结果得到一团抽象派奶泡
示例代码:咖啡订单计数器
class Counter {
private int orders = 0; // 总订单数
// 注意:这个方法存在原子性问题
public void addOrder() {
orders++; // 相当于三步:读值→改值→写回
}
// 正确写法应该像这样:
// public synchronized void addOrder() { orders++; }
}
// 注释说明:
// 1. orders++ 不是原子操作,可能被线程切换打断
// 2. 当两个线程同时读取orders=5,都会改成6,实际应该到7
// 3. 这就好比两个收银员同时处理同一张订单
1.2 可见性问题:失效的价目表
假设店长更新了价目表,但:
- 前台收银机缓存了旧价格(线程本地内存)
- 新来的兼职生没收到通知(未刷新内存)
- 顾客看到的价格各不相同(数据不一致)
// 价目表变量声明
boolean isPriceUpdated = false; // 无volatile修饰
// 店长线程
void updatePrice() {
loadNewPrices(); // 耗时操作
isPriceUpdated = true; // 标记已更新
}