多个线程抢同一个资源,结果乱了套
写程序时经常遇到这种情况:两个线程同时往一个计数器里加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 配合条件锁,既省资源又及时。