线程安全指多线程并发访问时行为始终符合预期、结果确定且一致;count++因非原子性(load-add-save三步)必然导致数据竞争;synchronized适合简单同步,ReentrantLock支持中断与超时;AtomicInteger仅对单变量原子操作无锁,volatile不保证原子性。

线程安全不是“加了锁就安全”,而是指多个线程并发访问某个类、方法或变量时,**行为始终符合预期,结果确定且一致**。比如 count++ 看似一行,实际分 load、add、save 三步——只要中间被其他线程插队,结果就错。这不是偶发 Bug,是必然发生的数据竞争。
为什么 count++ 一定不安全?
因为 JVM 不保证其原子性:两个线程可能同时读到 count = 5,各自加 1 后都写回 6,最终丢失一次递增。实测中,两个线程各执行 10000 次 count++,输出常为 12xxx 或 14xxx,而非预期的 20000。
- 根本原因:抢占式调度 + 非原子操作 + 共享可变状态
- 可见性问题:一个线程改了值,另一个线程可能还缓存在寄存器里,读不到最新值
- 指令重排序:JVM 或 CPU 可能调整执行顺序,破坏逻辑依赖(如初始化未完成就被其他线程看到)
synchronized 和 ReentrantLock 怎么选?
两者都能互斥执行临界区,但适用场景差异明显:
-
synchronized:自动加锁/解锁,不会忘记释放,适合简单同步(如包装一个计数器、单例 getInstance);但无法中断等待、不支持超时、默认非公平 -
ReentrantLock:需手动lock()/unlock()(必须在finally块中),但支持tryLock(1, TimeUnit.SECONDS)、lockInterruptibly()、公平锁等高级控制,适合高并发+需精细调度的场景 - 性能上,现代 JVM 对
synchronized优化极好(偏向锁→轻量级锁→重量级锁),低竞争下甚至比ReentrantLock更快
用 AtomicInteger 就真无锁了吗?
是的,但仅限于它支持的原子操作(如 incrementAndGet()、compareAndSet())。它底层靠 CPU 的 CAS 指令实现,没有阻塞、没有上下文切换开销。
立即学习“Java免费学习笔记(深入)”;
- 适用:单个变量的读-改-写(如计数器、序列号生成)
- 不适用:需要原子地更新多个变量,或涉及复杂逻辑(如 “先查库存再扣减再记录日志”)——这时 CAS 无法覆盖整个业务单元,仍得用锁
- 注意:
getAndIncrement()和incrementAndGet()返回值不同,别混淆
volatile 能替代锁吗?
不能。它只解决**可见性**和**禁止重排序**,不解决**原子性**。
- 典型误用:
volatile boolean flag+flag = !flag—— 这仍是读+写两步,多线程下会出错 - 正确用法:状态标志(如
running)、双重检查单例中的 instance 引用(配合final字段保证构造完成可见) - 记住:
volatile是“轻量级同步”,不是“轻量级锁”
真正难的从来不是“怎么加锁”,而是判断“哪里需要同步”“哪些状态该共享”“哪些其实可以隔离”。比如用 ThreadLocal 避免共享、用不可变对象(final 字段+无 setter)彻底消除风险——这些设计决策,比写对一个 synchronized 块影响更深远。










