原子性指对其他线程看起来不可分割;可见性指避免线程缓存过期副本;有序性指禁止jvm/cpu重排序破坏多线程逻辑;oop并发下需将状态与同步策略绑定。

原子性不是“不可分割”,而是“对其他线程看起来不可分割”
Java里int i++看似简单,实际是读-改-写三步,多线程下可能被中断,导致结果丢失。真正原子的操作只有:基本类型赋值(除long和double在32位JVM上)、volatile读写、以及java.util.concurrent.atomic包下的类操作。
实操建议:
- 别依赖
i++或list.add()的原子性——它们都不是 - 计数器优先用
AtomicInteger,而不是synchronized包裹普通int -
AtomicReference适合封装对象引用的原子更新,比如无锁栈/队列实现 - 注意
AtomicInteger.getAndIncrement()返回旧值,incrementAndGet()返回新值,选错会导致逻辑 bug
可见性失效不是“没刷新”,而是“线程缓存了过期副本”
JVM允许每个线程把变量副本放在本地内存(如CPU缓存、寄存器),不强制立刻同步到主内存。所以线程A改了flag = true,线程B可能永远看不到——除非加volatile、synchronized或使用final字段安全发布。
常见错误现象:
立即学习“Java免费学习笔记(深入)”;
- while(
!stop)循环卡死,即使另一线程已设stop = true - 对象构造未完成就被其他线程看到(DCL单例中忘记
volatile修饰实例字段)
关键点:
-
volatile只保证单个变量的可见性和禁止重排序,不保证复合操作原子性 -
synchronized块退出时会刷出所有本地修改,进入时会清空本地缓存——这是它附带的可见性保障 - 对象一旦通过
final字段正确构造(且构造过程中没泄露this),其他线程看到该对象时,一定能看见final字段的初始化值
有序性被破坏不等于“代码乱跑”,而是“JVM或CPU重排了指令”
为了优化性能,JVM和CPU可能调整指令执行顺序,只要不影响单线程语义。但多线程下,这种重排序会让某些依赖顺序的逻辑出错。例如双重检查锁中,instance = new Singleton()可能被拆成:分配内存→写final字段→设置instance引用;而重排序后变成:分配内存→设置instance引用→写final字段——这时另一个线程可能拿到未初始化完成的对象。
控制手段有限且精准:
-
volatile写之前的所有操作,不能重排序到该写之后;volatile读之后的所有操作,不能重排序到该读之前 -
synchronized块内语句不会被重排到块外,但块之间仍可能重排 - 不要试图靠
Thread.sleep()或空循环“等一等”来解决有序性问题——它既不禁止重排,也不保证可见性
OOP在多线程下不是失效,而是“封装边界被并发穿透”
一个Counter类封装了private int count,但若暴露getCount()和increment()两个非原子方法,外部调用c.getCount() + 1再c.increment()就完全绕过了封装意图。OOP的“数据+行为绑定”在并发场景下需要升级为“状态+同步策略绑定”。
设计时必须明确回答:
- 这个类是否预期被多个线程共享?如果不是,就加文档注明“not thread-safe”
- 如果要线程安全,是用
synchronized方法(简单粗暴)、ReentrantLock(支持条件、可中断)、还是无锁结构(高竞争下更优)? - 是否允许弱一致性?比如
ConcurrentHashMap的size()不保证实时准确,但迭代时不抛ConcurrentModificationException
最容易被忽略的是:final字段的初始化时机、对象发布方式(逃逸分析失败导致堆上对象被提前看到)、以及日志/监控等副作用代码意外破坏同步边界。









