繁体   English   中英

Java并发访问字段,不使用volatile的技巧

[英]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操作尚未推送到某些共享内存区域?

评论答案:

  • 线程仅在只读后修改临时映射。
  • 由于某些特殊的JAXB设置,我无法将List转换为Map,这使得从一开始就没有Map是可行的。

是否有可能一个线程将新的临时映射分配给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.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM