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