[英]Java concurrent access to field, trick to not use volatile
前言:我知道在大多数情况下,使用易失性字段不会产生任何可衡量的性能损失,但是这个问题更具理论性,并且针对具有极高一致性支持的设计。
我有一个字段,它是构造后填充的List<Something>
。 为了节省性能,我想将List转换为只读Map。 这样做在任何时候都至少需要一个易失的Map字段,以便使所有线程可见更改。
我正在考虑执行以下操作:
Map map; public void get(Object key){ if(map==null){ Map temp = new Map(); for(Object value : super.getList()){ temp.put(value.getKey(),value); } map = temp; } return map.get(key); }
即使它们以串行方式进入get块,这也可能导致多个线程生成映射。 如果线程在映射的不同相同实例上工作,这将不是什么大问题。 更让我担心的是:
是否有可能一个线程将新的临时映射分配给map字段,然后另一个线程看到该map!=null
并因此访问map字段而不生成新的临时映射,但令我惊讶的是发现该映射为空,因为put操作尚未推送到某些共享内存区域?
评论答案:
是否有可能一个线程将新的临时映射分配给map字段,然后另一个线程看到该
map!=null
并因此访问map字段而不生成新的临时映射,但令我惊讶的是发现该映射为空,因为put操作尚未推送到某些共享内存区域?
是的,这绝对有可能; 例如,一个优化的编译器实际上可以完全摆脱局部temp
变量,并且只要在异常情况下将map
恢复为null
,就可以一直使用map
字段。
同样,线程也可能会看到仍然未完全填充的非空,非空map
。 并且,除非您的Map
类经过精心设计以允许同时进行读写(或使用synchronized
以避免发生问题),否则,如果一个线程调用其get
方法而另一个线程调用put
,则您也可能会得到奇怪的行为。
您可以在ctor中创建Map并将其声明为final吗? 前提是您不泄漏地图,以便其他人可以对其进行修改,这足以使您的get()安全地被多个线程共享。
除了前面提到的可见性问题外,原始代码还有另一个问题,即。 它可以在这里抛出NullPointerException:
return this.map.get(key)
这是违反直觉的,但这是您从错误同步的代码中可以期望的。
防止这种情况的示例代码:
Map temp;
if ((temp = this.map) == null)
{
temp = new ImmutableMap(getList());
this.map = temp;
}
return temp.get(key);
当您真的怀疑其他线程是否可以读取“半完成”映射(我认为不是,但从不说永不;-)时,可以尝试使用此方法。
地图为空或完整
static class MyMap extends HashMap {
MyMap (List pList) {
for(Object value : pList){
put(value.getKey(), value);
}
}
}
MyMap map;
public Object get(Object key){
if(map==null){
map = new MyMap (super.getList());
}
return map.get(key);
}
还是有人看到一个新引入的问题?
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.