好睿思指南
霓虹主题四 · 更硬核的阅读氛围

线程同步机制常见问题:多线程编程踩过的坑

发布时间:2025-12-12 08:44:55 阅读:43 次

多个线程抢同一个资源,结果乱了套

写程序时经常遇到这种情况:两个线程同时往一个计数器里加1,结果发现加了两次却只涨了一次。这是因为没有做好线程同步,数据被覆盖了。比如银行账户转账,两个人同时取钱,余额没及时锁住,就可能出现超支的情况。

这种问题在实际开发中特别常见,尤其是在高并发的场景下,像电商抢购、秒杀活动,要是不处理好,用户可能买到负库存的商品。

用synchronized就能解决?不一定

很多人一上来就给方法加 synchronized,觉得这样就安全了。但其实如果锁的粒度太大,整个系统会变慢。比如一个缓存服务,所有读操作也被锁住,明明读不需要互斥,结果响应时间翻了好几倍。

更合理的做法是缩小锁的范围,只锁关键代码段。例如:

public class Counter {
private int count = 0;

public void increment() {
synchronized(this) {
count++;}
}
}

这样只有修改 count 的时候才加锁,其他操作不受影响。

死锁是怎么悄悄发生的

两个线程各自拿着一把锁,又等着对方手里的锁,结果谁也动不了。就像两个人过独木桥,都走到中间,谁也不让,只能僵在那里。

常见的例子是转账操作:线程A从账户X转到Y,先锁X再锁Y;线程B同时从Y转到X,先锁Y再锁X。一旦时机凑巧,两个线程各持一锁,互相等待,程序就卡死了。

避免的办法是约定锁的顺序,大家都按账户ID从小到大加锁,就不会出现循环等待。

volatile能代替锁吗

volatile 关键字能保证变量的可见性,一个线程改了,别的线程马上能看到。但它不能保证原子性。比如 i++ 看似一条语句,其实是读、加、写三步。即使 i 是 volatile 的,多个线程同时操作还是会出错。

这时候得用 AtomicInteger 这种原子类,内部用了CAS机制,既能保证效率又能保证正确性。

private AtomicInteger count = new AtomicInteger(0);

public void increment() {
count.incrementAndGet();
}

这种写法比加锁更轻量,适合简单的计数场景。

信号量和条件变量容易用混

有时候需要控制同时访问某个资源的线程数量,比如数据库连接池最多支持10个连接。这时候用信号量(Semaphore)就很合适,它像发令牌,领到才能干活。

而条件变量(Condition)是用来协调线程之间的执行顺序的,比如生产者通知消费者“有新任务了”。混淆这两者,可能导致资源浪费或者线程一直等待。

实际项目中见过有人用 while(true) 死循环加 sleep 来轮询状态,其实完全可以用 await/notify 配合条件锁,既省资源又及时。