通过阅读JLS并查看多个场景后,我无法确定Java内存模型遵循线程内语义的规则
仅出于示例目的考虑此代码:
public class CharIndexer {
public Map<char, int> charLastIndex;
public void changeCount(String phrase) {
Map<char, int> newCharLastIndex = new HashMap<char, int>();
for (int i = 0; i < s.length(); i++){
newCharLastIndex.put(s.charAt(i),i);
}
charLastIndex = newCharLastIndex;
}
}
在多线程持有对同一CharIndexer实例的引用的场景中,读取字段charLastIndex,而其中一个调用changeCount方法。
如果对charLastIndex字段的赋值(方法的最后一个赋值)在for块之前完成,那么它是否是一个有效的重新排序?
这使得阅读线程可以看到仍然没有填充的地图。
虽然我同意应该使用volatile关键字明确保证可见性,但是线程内语义是否允许这样的重新排序?
两个命令的单线程执行都会授予相同的结果,但是哪些规则确实可以控制内部线程重新排序,从而避免在此实现中对两个块进行重新排序:
public class CharIndexer {
public Map<char, int> charLastIndex;
public void changeCount(String phrase) {
Map<char, int> newCharLastIndex = new HashMap<char, int>();
for (int i = 0; i < s.length(); i++){
newCharLastIndex.put(s.charAt(i),i);
}
// just changing values around
foreach(Map.Entry<char,int> charEntry : newCharLastIndex) {
charEntry.setValue(charEntry.getValue() * 10);
}
charLastIndex = newCharLastIndex;
}
}
我试图了解JIT分析的进展程度,或者我是否对线程内语义的特定规则集一无所知。
答案 0 :(得分:1)
如果没有发生边界或有效的同步块,则另一个线程看到的操作顺序可能是乱序的。
解释它的最佳方式是,您的HashMap
对象和CharIndexer
对象可能位于计算机内存的不同部分。
在多CPU服务器上,内存访问由每个CPU独立缓存,因此当CPU#1运行changeCount()
方法时,所有操作都在CPU 1缓存中完成。
最终,该缓存刷新到主RAM,一旦他们从主RAM重新加载他们的缓存,其他CPU就可以看到它。
但是,持有HashMap
的缓存部分和持有CharIndexer
的缓存部分可能不会同时刷新+重新加载。因此,在看到对引用的CharIndexer
的更新之前,CPU#2可能会看到HashMap
的更新。
这就是为什么你需要确保HashMap
的构建发生在<{1}}赋值之前,或者两者都将发生 - 在之前使用charLastIndex
值。