写代码的时候,你有没有遇到过数据莫名其妙变了?比如两个用户同时下单,结果库存减成了负数?这八成是线程安全没搞明白。很多人一听“线程安全”,第一反应就是:赶紧加锁!但加锁真能一劳永逸吗?
什么是线程安全?
简单说,多个线程同时操作同一个资源,结果还能对得上,就叫线程安全。比如银行账户,两个人同时取钱,余额不能算错。但如果没处理好,就会出现“超卖”“重复扣款”这种坑。
加锁是唯一办法吗?
不一定。加锁确实常见,比如用 synchronized 或 ReentrantLock 把关键代码包起来,确保同一时间只有一个线程能执行:
private int count = 0;
public synchronized void increment() {
count++;
}
这样看起来稳妥了,但代价也不小——锁多了,程序变慢,甚至可能死锁。就像超市只有一个收银台,人一多全堵着,用户体验直接崩。
不加锁也能线程安全?
当然可以。比如用 AtomicInteger 这种原子类,底层靠的是 CAS(比较并交换),不用传统锁也能保证操作的原子性:
private AtomicInteger count = new AtomicInteger(0);
public void increment() {
count.incrementAndGet();
}
这种方式在高并发下性能更好,就像开了多个自助结账通道,效率自然上去了。
那到底要不要加锁?
看场景。如果只是简单的计数、状态切换,优先考虑原子类或 volatile。如果涉及多个变量的复杂逻辑,比如“检查余额→扣款→记录日志”这一整套流程,那就得靠锁来保证整体一致性。
还有一种情况:数据本身不可变。比如 String 或 LocalDate,天生线程安全,根本不用操心锁的事。就像你拿一张打印好的发票,传给谁都不会变样。
别为了“安全”拖垮性能
有些开发者一上来就给方法加 synchronized,不管三七二十一。结果服务一上线,吞吐量掉一半。其实可以先评估风险:这个变量会被多个线程改吗?改动是不是原子操作?有没有现成的无锁工具能用?
就像家里防盗,不是每扇窗都得焊铁笼子。有的装个报警器就行,有的干脆本来就没窗户。技术选择也一样,别盲目堆方案。
线程安全不等于必须加锁。理解机制,选对工具,才能既省资源又避坑。