十四、并发编程

58 阅读2分钟

🔥 并发编程

"多线程代码不是难写,是难活到上线"

这一章是所有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

💡 哲学三问(面试装逼用)

  1. 锁与性能如何权衡? "先正确再优化,无锁算法 > 细粒度锁 > 粗粒度锁,参考Disruptor环形队列设计"

  2. 如何证明线程安全? "JCStress测试工具 + 代码评审时追问『这段代码如何保证可见性?』"

  3. 分布式并发怎么玩? "从Redis分布式锁CAS乐观锁,最终靠Kafka顺序消息躺平"


🔮 终极生存法则

"所有并发Bug都是薛定谔的猫——在你盯着日志看的时候它永远不会出现。" 最佳实践:

  • 能不用多线程就别用(改用单线程+事件循环)
  • 必须用时,所有共享变量默认为final
  • 代码上线前用Chaos Monkey随机杀死线程