浅拷贝不解决并发问题,仅复制对象一层结构,内部可变子对象仍共享引用,导致多线程并发修改同一子对象引发竞态条件。

浅拷贝本身不解决并发问题,它只负责复制对象结构的“一层”,对内部可变子对象仍共享引用。当多个线程通过不同副本读写同一个子对象属性时,就会直接触发竞态条件——这不是拷贝逻辑的缺陷,而是设计上未隔离可变状态的必然结果。
内存层面:两个副本指向同一块堆内存
浅拷贝后,原始对象和副本在堆中是两个独立对象,但它们的某个字段(比如 dataList)可能都持有对同一个 ArrayList 实例的引用。这意味着:
- 所有线程对这个 ArrayList 的 add()、remove()、clear() 操作,都会作用于同一内存地址
- 即使每个线程操作的是自己的“父对象”,只要父对象的字段引用了共享子对象,就等同于多线程直连同一容器
- JVM 不会自动加锁或同步访问,也不会检测这种逻辑上的共享关系
线程行为:典型竞态场景还原
假设 User 类含一个可变 List
- 线程 A 执行 userA.getRoles().add(new Role("admin"))
- 线程 B 同时执行 userB.getRoles().remove(0)
- 由于 userA.roles == userB.roles,实际是对同一 ArrayList 并发修改
- 结果可能是 ConcurrentModificationException、数据丢失、索引越界,或静默的数据错乱
为什么 synchronized 或 volatile 都救不了它
加锁对象若选错,完全无效:
- 对 userA 或 userB 加锁,只阻止其他线程操作该副本本身,不影响对其共享子对象的访问
- volatile 只保证字段引用的可见性,不保证其指向对象内部状态的线程安全
- 真正需要同步的是那个被共享的 ArrayList 实例本身,或改用 CopyOnWriteArrayList、ConcurrentHashMap 等线程安全容器
安全替代路径:不靠拷贝,而靠隔离
与其在浅拷贝后拼命补同步,不如从源头切断共享:
- 返回防御性副本:getter 中用 new ArrayList(this.roles),让每次读都拿到新实例
- 使用不可变集合:如 ImmutableList.copyOf(this.roles)(Guava),天然杜绝修改
- 按需深拷贝关键字段:仅对确定会被修改的嵌套结构做 deepcopy,其余保持轻量引用
- 改用线程局部对象:结合 ThreadLocal
,确保每个线程始终操作专属实例











