写代码时经常听到“这个类是线程安全的”或者“这段逻辑要考虑并发问题”,听起来好像差不多,其实完全是两个层面的事。就像做饭时“食材新鲜”和“多人同时下厨”不是一回事,线程安全和并发也得分开看。
并发是场景,线程安全是应对
并发指的是多个任务在同一时间段内交替执行,比如你一边刷视频一边回微信,手机系统就在处理并发。程序里也一样,一个服务同时接100个用户请求,这些请求对应的线程可能同时操作同一个数据。
这时候问题就来了:如果两个线程同时修改同一个余额变量,一个减50,一个减30,原本1000的余额,结果可能只减了50或30,甚至变成奇怪的数字。这不是因为程序写错了语法,而是没有应对好并发带来的竞争。
线程安全是代码的“防踩踏设计”
线程安全指的是某个类、方法或代码块在多线程环境下执行时,依然能表现出正确的行为。就像地铁站的闸机,不管多少人同时刷卡,系统都能准确记录进出人数,不会漏记或多记。
举个例子,下面这个计数器就不是线程安全的:
public class Counter {
private int count = 0;
public void increment() {
count++; // 这一行其实包含读、加、写三步
}
public int getCount() {
return count;
}
}
因为 count++ 不是原子操作,两个线程可能同时读到相同的值,各自加1后再写回去,结果只增加了1次。要让它线程安全,可以加锁:
public class SafeCounter {
private int count = 0;
public synchronized void increment() {
count++;
}
public synchronized int getCount() {
return count;
}
}
加上 synchronized,就像给方法加了门,同一时间只能有一个线程进来,避免了数据冲突。
有并发不一定需要线程安全
也不是所有并发场景都要追求线程安全。比如你写的工具类只在单线程里用,就算它不安全也没关系。就像家里只有一个厨房,没人抢灶台,自然不需要排班表。
反过来,即使没有并发,谈线程安全也没意义。单线程环境里,代码怎么跑都出不了问题,安全不安全根本没区别。
常见误区
很多人以为用了线程安全的集合类(比如 ConcurrentHashMap)就万事大吉,其实不然。它只能保证自己内部操作是安全的,如果你在外面做“先查后改”这种组合操作,依然可能出问题。
比如:
if (!map.containsKey("key")) {
map.put("key", "value"); // 中间可能被其他线程插队
}
即便 map 是 ConcurrentHashMap,这段代码仍然可能让两个线程同时写入,导致重复操作。正确的做法是直接用 putIfAbsent 这种原子方法。
说到底,并发是现实,线程安全是应对策略。搞清楚谁是因,谁是果,写多线程代码时才不会手忙脚乱。