《Java并发编程实战》5.18示例指令是否会在编译器优化时被重排序
问题结论
cache.put(arg, ft) 不可能被编译器重排序到 cache.get(arg) 之前执行,这是JMM规范和Java API约定双重约束的结果,具体原因如下:
具体原因分析
- 单线程as-if-serial语义的强约束
编译器的重排序优化必须遵守基础规则:无论怎么调整指令顺序,都不能改变单线程场景下的代码执行结果。如果把put操作提前到get之前,第一次调用compute方法时,get会直接拿到刚放入的FutureTask对象,直接跳过if(f == null)分支,不会执行ft.run(),最终调用f.get()时会永久阻塞,这显然改变了单线程下的执行结果,这种重排序从根本上就是不合法的。 - ConcurrentHashMap的同步操作排序约束
你提到的Doug Lea的那段表述,针对的是完全没有同步保护的普通共享字段读写场景,比如直接操作无volatile修饰的普通对象引用或者基础类型字段。但ConcurrentHashMap本身是线程安全容器,它的所有对外方法都内置了同步语义:
ConcurrentHashMap的get操作具备volatile读的内存语义ConcurrentHashMap的put操作具备volatile写的内存语义
JMM明确规定,同一个线程内的volatile读写操作不允许被编译器随意重排序,更不可能把volatile写(put)重排序到同一个对象的volatile读(get)之前。
- final域的额外保障
代码中cache是被final修饰的成员变量,JMM对final域有专门的安全初始化保证:只要对象是正确构造的(也就是构造方法没有逸出),那么不需要额外同步就可以保证所有线程都能看到cache被正确初始化后的引用值,不存在普通共享变量的初始化可见性问题。
补充说明
Memoizer3的问题并不在重排序风险上,它的缺陷是「先检查再执行」的竞态条件:两个线程同时判断同一个key的f == null为真时,会重复计算同一个值,这也是后续版本Memoizer用putIfAbsent替代put来解决的问题。
内容的提问来源于stack exchange,提问作者bimjhi
相关产品推荐
相关产品推荐

