🔥 并发编程
"多线程代码不是难写,是难活到上线"
这一章是所有Java/C++/Go工程师的噩梦,我们直接上必杀技清单+现实代码例子+反杀Bug套路,建议搭配降压药阅读💊。
💀 并发四大死刑犯(附减刑指南)
| 罪名 | 症状举例 | 解药 | 代码对比 |
|---|---|---|---|
| 竞态条件 | if (x==1) { x++ } 多个线程同时执行导致x最终可能≠2 | 加锁 / 原子操作 | [下文代码1] |
| 死锁 | A等B,B等A,全班等你俩吃饭 | 按固定顺序上锁 / 超时释放 | [下文代码2] |
| 内存可见性 | 线程A改了变量,线程B看不到 | volatile / synchronized | [代码3] |
| 线程泄漏 | 线程池任务卡死,新任务排队到天荒地老 | 正确关闭资源 / 监控线程状态 | [代码4] |
🛠 代码刑具房(现实例子+解法)
1. 竞态条件:余额扣减翻车现场
// 翻车版(100个线程并发扣款,最终余额可能>0)
class Wallet {
private int balance = 100;
public void deduct() {
if (balance >= 10) {
Thread.sleep(10); // 模拟处理延迟
balance -= 10;
}
}
}
// 救赎版(原子操作)
class SafeWallet {
private final AtomicInteger balance = new AtomicInteger(100);
public void deduct() {
balance.updateAndGet(b -> b >= 10 ? b - 10 : b);
}
}
2. 死锁:转账作死小能手
# 死锁版(Alice和Bob互相转账)
def transfer(a, b, amount):
with a.lock: # 先锁A账户
with b.lock: # 再锁B账户
a.balance -= amount
b.balance += amount
# 求生版(按照ID顺序上锁)
def safe_transfer(a, b, amount):
first, second = sorted([a, b], key=lambda x: x.id)
with first.lock:
with second.lock:
a.balance -= amount
b.balance += amount
3. 内存可见性:flag失效之谜
// 绝望版(线程B永远看不到flag变化)
bool flag = false;
void threadA() {
flag = true; // 写入后可能卡在CPU缓存
}
void threadB() {
while (!flag); // 读不到新值
printf("Finished");
}
// 觉醒版(C++用atomic)
std::atomic<bool> flag(false);
🚨 并发防御工具箱
| 武器 | 适用场景 | 示例 |
|---|---|---|
| 不可变对象 | 共享数据只读 | Java的String / Python的tuple |
| 线程局部变量 | 避免同步开销 | ThreadLocal<User> |
| 并发集合 | 替代手动同步容器 | ConcurrentHashMap |
| 协程 | IO密集型任务 | Python的asyncio |
💡 哲学三问(面试装逼用)
-
锁与性能如何权衡? "先正确再优化,无锁算法 > 细粒度锁 > 粗粒度锁,参考Disruptor环形队列设计"
-
如何证明线程安全? "JCStress测试工具 + 代码评审时追问『这段代码如何保证可见性?』"
-
分布式并发怎么玩? "从Redis分布式锁到CAS乐观锁,最终靠Kafka顺序消息躺平"
🔮 终极生存法则
"所有并发Bug都是薛定谔的猫——在你盯着日志看的时候它永远不会出现。" 最佳实践:
- 能不用多线程就别用(改用单线程+事件循环)
- 必须用时,所有共享变量默认为
final- 代码上线前用Chaos Monkey随机杀死线程