ConcurrentHashMap读取可变对象与compute方法线程安全问题问询
问题解答
你的结论完全正确:确实存在读取操作仅获取到部分修改结果(即只看到a=21,b仍为旧值)的情况,原因如下:
1. ConcurrentHashMap的线程安全边界有限
ConcurrentHashMap保证的是自身映射表结构的线程安全,比如compute方法会原子性地处理单个键的映射关系(其他线程无法在你执行compute的过程中修改该键对应的引用),但它不会为你存入的值对象提供线程安全保障。它的"检索无需加锁"特性,仅针对映射表中键值对的引用读取,和值对象内部的字段无关。
2. 可变值对象的修改无原子性与可见性保障
你定义的PlainObject是可变类,且字段a、b既没有volatile修饰,也没有同步保护:
- 在
compute的lambda中,v.a = 21和v.b = 18是两个独立的赋值操作,中间的"其他操作"会拉长两者的时间间隔; - 由于没有内存屏障或同步措施,这两个赋值的结果可能不会立即同步到主内存,其他线程的读取操作可能因为缓存一致性、指令重排序等原因,只看到
a的修改,却看不到b的修改。
3. 无锁读取的本质
ConcurrentHashMap.get的无锁读取,只是保证你能拿到该键对应的最新值对象引用,但拿到引用后对对象字段的读取,完全遵循Java内存模型的普通规则——没有同步的话,无法保证看到其他线程对字段的修改结果,更无法保证多个字段修改的原子性。
修复方案
如果要避免这种部分可见的问题,可以选择以下方式:
- 将
PlainObject改为不可变类:每次修改都返回新的实例,compute中返回新对象,这样ConcurrentHashMap的原子性就能保证读取到完整的新状态; - 为字段添加
volatile修饰:保证单个字段修改的可见性,但注意两个字段的修改仍非原子,若需要同时修改a和b且让读取看到一致状态,还需配合同步块; - 在修改/读取
PlainObject时加锁:比如用synchronized包裹字段的修改和读取逻辑,保证操作的原子性与可见性。
内容的提问来源于stack exchange,提问作者Bula
相关产品推荐
相关产品推荐

