1

前言:我知道在大多数情况下,使用 volatile 字段不会产生任何可测量的性能损失,但这个问题更具理论性,并且针对具有极高准确性支持的设计。

我有一个字段,List<Something>它是在构造后填充的。为了节省一些性能,我想将列表转换为只读地图。在任何时候这样做都需要至少一个可变的 Map 字段,因此使所有线程的更改可见。

我正在考虑执行以下操作:

地图地图;

公共无效获取(对象键){
    如果(地图==空){
        地图临时=新地图();
        for(对象值: super.getList()){
            temp.put(value.getKey(),value);
        }
        地图=温度;
    }
     返回 map.get(key);
}

这可能会导致多个线程生成映射,即使它们以序列化方式进入 get 块。如果线程在地图的不同相同实例上工作,这将不是什么大问题。更让我担心的是:

是否有可能一个线程将新的临时映射分配给映射字段,然后第二个线程看到它map!=null并因此访问映射字段而不生成新的,但令我惊讶的是发现映射是空的,因为 put 操作哪里还没有推送到某个共享内存区域?

评论回复:

  • 线程仅在只读之后修改临时映射。
  • 由于一些特殊的 JAXB 设置,我必须将 List 转换为 Map,这使得从 Map 开始变得不可行。
4

4 回答 4

3

是否有可能一个线程将新的临时映射分配给映射字段,然后第二个线程看到它map!=null并因此访问映射字段而不生成新的,但令我惊讶的是发现映射是空的,因为 put 操作哪里还没有推送到某个共享内存区域?

是的,这是绝对可能的;例如,优化编译器实际上可以完全摆脱局部temp变量,并且一直使用该map字段,前提是在发生异常的情况下恢复mapnull

类似地,一个线程也可以看到一个非空、非空map但未完全填充的。除非您的Map类经过精心设计以允许同时读取和写入(或用于synchronized避免该问题),否则如果一个线程正在调用其get方法而另一个线程正在调用其put.

于 2012-03-25T21:39:41.700 回答
1

您可以在 ctor 中创建您的 Map 并将其声明为最终版本吗?如果您不泄漏地图以便其他人可以修改它,这足以使您的 get() 可以由多个线程安全共享。

于 2012-03-25T21:17:46.037 回答
0

当您真的怀疑其他线程是否可以读取“半完成”地图时(我不这么认为,但永远不要说永远;-),您可以试试这个。

地图为空或完整

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);
 }

还是有人看到新引入的问题?

于 2012-03-26T12:22:33.563 回答
0

除了前面提到的可见性问题之外,原始代码还有另一个问题,即。它可以在这里抛出 NullPointerException:

return this.map.get(key)

这是违反直觉的,但这是您可以从不正确同步的代码中得到的结果。

防止这种情况的示例代码:

Map temp;
if ((temp = this.map) == null)
{

    temp = new ImmutableMap(getList());
    this.map = temp;
}
return temp.get(key);
于 2016-06-06T12:23:04.967 回答