JMM重排序与缓存同步场景下,JCStress测试为何未出现-1结果?
JCStress测试中currentMap.get未返回null的原因
测试代码
public class Cache_01_Sample { @JCStressTest @Outcome(expect = ACCEPTABLE, desc = "9") @Outcome(id = "-1", expect = FORBIDDEN, desc = "0") @State public static class DemoCache { private Map<Integer, Integer> currentMap = new HashMap<>(); private int size = 100000; public DemoCache() { for (int i = 0; i < size; i++) { currentMap.put(i, i); } } @Actor public void get(I_Result result) { int i = new Random().nextInt(0, size); Integer value = this.currentMap.get(i); if (null == value) { result.r1 = -1; } else { result.r1 = value; } } @Actor public void refresh() { Map<Integer, Integer> map = new HashMap<>(); for (int i = 0; i < size; i++) { map.put(i, i); } currentMap = map; } } }
问题背景
基于JMM重排序与缓存同步机制开展上述JCStress测试,结果显示-1(即currentMap.get返回null的情况)从未出现,希望了解该现象的原因。
原因分析
- 单线程内的执行顺序保证:
refresh方法中,新HashMap的所有put操作必然在currentMap = map的引用赋值之前完成。JMM的as-if-serial语义确保单个线程内的操作会按照代码逻辑顺序执行,不会出现“先把空map赋值给currentMap,再往里面put数据”的重排序。因此,当其他线程看到currentMap指向新map时,这个新map已经是完整初始化的,包含了0到99999的所有键值对。 - 旧map的完整性:即使
get线程还在访问旧的currentMap,旧map在构造方法中已经完成了所有键值对的初始化,随机生成的i(0到99999)必然是旧map中存在的键,因此get操作不会返回null。 - 无半初始化map被访问:currentMap的引用赋值是原子操作,线程要么看到旧的完整map,要么看到新的完整map,不存在访问“正在初始化的半完整map”的可能。
综上,无论get线程访问的是旧map还是新map,目标键都必然存在,因此currentMap.get永远不会返回null,测试中也就不会出现-1的结果。
内容的提问来源于stack exchange,提问作者刘警君
相关产品推荐
相关产品推荐

