是
final Map<Integer,Map<String,Integer>> status = new ConcurrentHashMap<Integer, Map<String,Integer>>();
Map<Integer,Map<String,Integer>> statusInner = new ConcurrentHashMap<Integer, Map<String,Integer>>();
status.put(key,statusInner);
与
相同volatile Map<Integer,Map<String,Integer>> status = new ConcurrentHashMap<Integer, Map<String,Integer>>();
Map<Integer,Map<String,Integer>> statusInner = new ConcurrentHashMap<Integer, Map<String,Integer>>();
status.put(key,statusInner);
如果内部Map由不同的线程访问?
或甚至是这样的要求:
volatile Map<Integer,Map<String,Integer>> status = new ConcurrentHashMap<Integer, Map<String,Integer>>();
volatile Map<Integer,Map<String,Integer>> statusInner = new ConcurrentHashMap<Integer, Map<String,Integer>>();
status.put(key,statusInner);
如果它不是“级联”地图,那么final和volatile最终会产生同样的效果,即所有线程总是看到Map的正确内容...但是如果Map iteself包含会发生什么一个地图,如示例中所示......我如何确保内部地图正确“记忆bar”?
坦克! 汤姆
答案 0 :(得分:9)
volatile
仅影响其他线程读取其附加变量值的能力。它决不会影响另一个线程查看地图的键和值的能力。例如,我可以有一个volatile int[]
。如果我改变参考 - 即。如果我更改它指向的实际数组 - 其他线程读取数组保证看到该更改。但是,如果我更改数组的第三个元素,则不会做出这样的保证。
如果status
为final
,则包含类的构造会与任何后续读取创建happens-before
关系,因此他们可以看到状态值。同样,对volatile
变量的任何读取都可以保证看到最新的参考分配。这与你经常交换实际地图不同,更像是你只是改变键,整个地图对象保持不变。
对于这个问题,我们需要查阅ConcurrentHashMap
的文档:
检索操作(包括get) 一般不阻塞,所以可能重叠 更新操作(包括put 并删除)。检索反映了 最近完成的结果 更新操作持有他们的 发作。
这有点奇怪的措辞,但要点是任何get
操作,其在某些put
操作返回后的保证看到该放置的结果。所以你甚至不需要外部地图上的volatile
; JLS:
只能看到引用的线程 在该对象之后的对象 已经完全初始化了 保证看到正确 该对象的初始化值 最后的领域。
外部地图上的final
就足够了。
答案 1 :(得分:4)
值得一看Google-Collections,尤其是MapMaker,可让您智能地设置和创建地图。能够设置弱值,实现更好的垃圾收集和到期时间,因此您可以使用Maps进行有效的缓存,非常棒。由于MapMaker使得(:p)具有与ConcurrentHashMap相同的属性,因此您可以对其线程安全感到满意。
final mapMaker = new MapMaker().weakValues(); //for convenience, assign
final Map<Integer,Map<String,Integer>> status = mapMaker.makeMap();
status.put(key, mapMaker.<String, Integer>makeMap());
请注意,您可能需要查看statusInner的定义,因为它似乎不正确。
答案 2 :(得分:1)
我认为这里最好的答案是volatile
不是确保线程安全的方法。
使用ConcurrentHashMap
几乎就是您所需要的。是的,如果可以,请引用顶级Map
final
,但无论如何都不需要volatile
。内部的第二级Map
引用是ConcurrentHashMap
正确的业务,并且假设它正确。