线程安全类的方法本身原子,但复合操作(如“先检查再更新”)仍非原子;需用computeIfAbsent、merge等原子API,或synchronized加锁保护临界区,避免仅依赖组合线程安全类。

线程安全类(如 ConcurrentHashMap、AtomicInteger)本身方法是原子的,但多个操作组合(比如“先检查再更新”)仍可能破坏原子性——这叫复合操作的非原子问题。仅靠使用线程安全类无法自动保证复合操作的原子性,必须额外加同步机制。
理解复合操作为何不安全
例如:用 ConcurrentHashMap 实现一个计数器,想实现“若 key 不存在则设为 1,否则加 1”:
if (!map.containsKey("a")) {
map.put("a", 1);
} else {
map.put("a", map.get("a") + 1);
}
这段代码即使 map 是线程安全的,containsKey 和 put 之间仍有时间窗口,多个线程可能同时判断为“不存在”,最终只写入 1 而非正确累加。
用原子类的 CAS 方法替代手动判断
优先选用支持原子复合操作的 API,避免手动拆解:
立即学习“Java免费学习笔记(深入)”;
在 Java 中初始化和管理阿里云 SDK客户端。包括单例模式、线程安全、endpoint 与 region 配置、VPC 终端节点、同步与异步等。
-
ConcurrentHashMap.computeIfAbsent(key, mappingFunction):原子地判断并初始化,适合“首次访问才创建”场景 -
ConcurrentHashMap.merge(key, value, remappingFunction):原子地合并值,适合计数累加 -
AtomicInteger.compareAndSet(expected, updated)或updateAndGet(operand):在循环中重试,实现自定义原子更新逻辑
例:安全计数
map.merge("a", 1, Integer::sum); // 原子完成“读+算+写”
必要时用显式同步保护临界区
当标准 API 不满足逻辑(如需跨多个 map 或混合 I/O 操作),必须用锁确保整个复合操作不可分割:
- 用
synchronized块包裹多步操作,锁对象建议是业务相关的、不变的实例(如 map 本身或专用 lock 对象) - 避免锁
this或公共类对象,防止外部干扰 - 尽量缩小同步范围,只锁真正共享且需一致性的代码段
例:
private final ConcurrentHashMap<String, Integer> map = new ConcurrentHashMap<>();
private final Object countLock = new Object();
void safeIncrement(String key) {
synchronized (countLock) {
int cur = map.getOrDefault(key, 0);
map.put(key, cur + 1);
}
}
避免过度依赖“组合线程安全类”
把多个线程安全类(如 ConcurrentHashMap + AtomicBoolean)简单拼在一起,并不能自动让它们的交互变原子。关键看操作是否涉及共享状态的**逻辑耦合**:
- 如果两个类的状态独立(如分别统计不同指标),通常无需额外同步
- 如果一个操作的结果影响另一个类的判断(如“map 中存在 key 才允许修改 flag”),就必须统一加锁或设计成单次原子调用
- 考虑用更高级抽象,如
StampedLock(支持乐观读)或ReadWriteLock,在读多写少场景提升并发性
不复杂但容易忽略:线程安全类只是工具,原子性保障永远取决于你如何组织操作——API 提供原子能力,你负责选择和封装正确的组合方式。

















