i++不是原子操作,因其被拆分为读取、计算、写回三步,多线程下易发生竞态导致结果错误;volatile仅保可见性不保原子性;synchronized、AtomicInteger、ThreadLocal等是常用线程安全方案。

多线程读写共享变量时,i++ 为什么不是原子操作?
因为 i++ 实际拆成三步:读取 i 的值 → 计算 i + 1 → 写回新值。多个线程可能同时读到相同旧值,各自加 1 后写回,导致最终只加了一次。
常见现象是:10 个线程各执行 1000 次 i++,结果远小于 10000;或者日志中出现重复 ID、计数跳变、ConcurrentModificationException 等。
- 不要依赖“看起来没出错”来判断线程安全——竞态条件(race condition)具有随机性和不可复现性
-
volatile能保证可见性,但不能解决复合操作的原子性问题(如i++、list.add()) - 局部变量天然线程安全;对象实例字段/静态字段才是风险点
用 synchronized 锁住临界区是最直接的控制方式
它通过 JVM 的 monitor 机制确保同一时刻只有一个线程能进入被保护的代码块或方法,适合逻辑清晰、争用不激烈的场景。
public class Counter {
private int count = 0;
public synchronized void increment() {
count++; // 这里是原子的
}
public synchronized int getCount() {
return count;
}
}
- 锁对象必须一致:方法级
synchronized锁的是当前实例(this),静态方法锁的是类对象(Counter.class) - 避免锁整个方法体——只包裹真正需要同步的语句,否则会严重拖慢吞吐量
- 不要用
String或常量池对象(如"lock")作锁,容易被外部误用导致锁失效或死锁
java.util.concurrent 包里的工具类更适合高并发场景
当需要频繁读写、或对性能敏感时,ReentrantLock、AtomicInteger、ConcurrentHashMap 等比 synchronized 更灵活高效。
立即学习“Java免费学习笔记(深入)”;
-
AtomicInteger用 CAS(Compare-And-Swap)实现无锁原子更新,适用于简单计数:counter.incrementAndGet() -
ReentrantLock支持可中断、超时、公平锁等特性,但需手动lock()/unlock(),忘记unlock()会导致死锁 -
ConcurrentHashMap不是“线程安全的 HashMap”,而是分段锁 + CAS 的高性能实现,get()完全无锁,put()只锁对应桶 - 避免把
ArrayList或HashMap包裹在synchronized块里使用——它们本身不提供迭代器一致性保障,仍可能抛ConcurrentModificationException
ThreadLocal 是隔离数据而非共享数据的思路
它为每个线程提供独立副本,彻底规避竞争。典型用途是保存用户上下文、数据库连接、格式化器(如 SimpleDateFormat)等非线程安全对象。
private static final ThreadLocalDATE_FORMAT = ThreadLocal.withInitial(() -> new SimpleDateFormat("yyyy-MM-dd"));
- 每次调用
get()都返回当前线程专属实例,无需同步 - 务必在业务结束时调用
remove(),尤其在线程池场景下,否则会造成内存泄漏(ThreadLocalMap中的 key 是弱引用,value 不是) - 不能用于传递参数或跨线程通信——子线程不会自动继承父线程的
ThreadLocal值,需显式使用InheritableThreadLocal
synchronized 补丁。










