很多人在写多线程程序时,常把“线程安全”和“并发”混为一谈。比如你开了个奶茶店小程序,两个顾客同时下单,结果库存减成了负数——这时候你可能会说:‘这不就是并发出的问题吗?’ 没错,但更准确地说,是你的代码不支持线程安全。
并发是场景,线程安全是能力
并发指的是多个任务在同一时间段内交替执行,比如系统同时处理多个用户请求。它描述的是一种运行状态,就像早高峰地铁站里人挤人,大家都在同一时间赶着进站。
而线程安全是指一段代码在被多个线程同时访问时,依然能保持正确的行为。就像地铁闸机,哪怕一百个人同时刷卡,它也能准确记录每个人进出,不会多扣钱也不会漏算。这就是线程安全的表现。
举个例子:银行取款机
假设两个人共用一张卡,同时在不同ATM上查余额并取钱。如果系统没有做线程安全控制,可能出现两人同时看到余额5000元,接着都取走3000元,最终账户变成-1000元。
问题不在“并发”本身——我们当然希望系统能同时服务多个操作。问题在于共享数据(余额)被多个线程修改时,缺乏保护机制。这时候你需要的是线程安全的实现方式,比如加锁。
代码对比更清楚
下面是一个非线程安全的计数器:
public class Counter {
private int count = 0;
public void increment() {
count++;
}
public int getCount() {
return count;
}
}
看似简单,但在高并发环境下,多个线程同时执行 count++,这个操作其实包含读、改、写三步,可能互相覆盖,导致最终结果不准。
改成线程安全版本:
public class SafeCounter {
private int count = 0;
private final Object lock = new Object();
public void increment() {
synchronized(lock) {
count++;
}
}
public int getCount() {
synchronized(lock) {
return count;
}
}
}
加上了同步锁,确保同一时间只有一个线程能进入关键区域。这时候即使并发量再大,结果也是可靠的。
不是所有并发都需要线程安全
如果每个线程操作的是自己的数据,比如每个用户独立会话、各自购物车,彼此不共享状态,那根本不需要考虑线程安全。并发照样跑得飞快,还省去了锁的开销。
换句话说,并发关注的是“能不能一起干”,线程安全关心的是“一起干会不会乱”。一个是并行的规模,一个是共享资源的防护。
现代开发中的常见做法
现在很多人用 ConcurrentHashMap 而不是 HashMap,用 AtomicInteger 替代普通 int 计数,就是为了在高并发场景下保证线程安全。这些类底层已经做好了同步处理,开发者不用从头造轮子。
另外,函数式编程提倡不可变对象(immutable),也是为了避开线程安全问题。一个对象创建后就不能改,大家只读不写,自然不怕冲突。
理解这一点,你在设计系统时就会更有分寸:不是一上来就加锁,而是先看有没有共享可变状态。没有共享,就不需要安全;有并发需求,才考虑如何安全地并发。