Java并发编程中的线程安全与MutableObject

MutableObject 在多线程下天然不安全,因其字段可变且通常缺乏同步控制,易引发竞态条件、状态不一致及JVM重排序问题;需通过 synchronized、ReadWriteLock、Atomic 类或安全发布机制来保障线程安全。

为什么 MutableObject 在多线程下天然不安全

因为它的字段可变且通常未加同步控制。只要多个线程同时调用 setX()getX() 或任意修改状态的方法,就可能触发竞态条件——比如一个线程刚读到旧值,另一个线程已覆盖它,导致丢失更新或读到中间态。

典型表现包括:

  • 返回值与预期不符(如 counter.get() == 5 却实际被改过 10 次)
  • 对象内部字段出现不一致(如 name 已更新但 id 还是旧值)
  • JVM 重排序让其他线程看到部分构造完成的对象(尤其在未正确发布时)

synchronized 能保护 MutableObject,但要注意锁粒度

粗粒度锁(如整个方法加 synchronized)能防止并发修改,但会严重限制吞吐;细粒度锁(如只锁关键字段操作块)更高效,但容易漏锁或死锁。

实操建议:

  • 优先对实例方法加 synchronized,而非静态方法,除非状态是类级别共享的
  • 避免在同步块内调用外部可变对象的方法(可能引发锁顺序反转)
  • 若对象仅用于读多写少场景,考虑用 java.util.concurrent.locks.ReadWriteLock
public class Counter {
    private int value = 0;
    public synchronized void increment() { // 锁住 this 实例
        value++;
    }
    public synchronized int get() {
        return value;
    }
}

AtomicInteger 替代简单计数类比写 synchronized 更轻量

MutableObject 只封装单个可原子更新的字段(如计数器、标志位),AtomicIntegerAtomicReference 等比手动加锁更高效,且避免了锁竞争和上下文切换开销。

注意点:

  • AtomicIntegergetAndIncrement() 是原子的,但复合操作如 “先读再判断后写” 仍需额外同步
  • compareAndSet(expected, updated) 是底层 CAS 原语,适合实现无锁算法,但失败时需自行重试逻辑
  • 不能直接用 AtomicInteger 替代含多个关联字段的 MutableObject(比如 Person 同时有 nameage

发布 MutableObject 实例前必须确保正确初始化与可见性

即使类内部用了 synchronizedvolatile,如果构造完成后未安全发布(如赋值给静态字段、放入全局容器、作为参数传给其他线程),其他线程仍可能看到未完全初始化的对象。

安全做法包括:

  • 构造完立即赋值给 final 字段(利用 final 域的初始化安全性)
  • 通过 static 工厂方法 + synchronizedvolatile 静态引用发布
  • 使用 java.util.concurrent.ConcurrentHashMap 存储,其 put

    保证发布可见性

最容易被忽略的是:把 new MutableObject() 直接塞进 ArrayList 后交给其他线程——这不构成安全发布。