简体   繁体   English

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

[英]Java concurrent access to field, trick to not use volatile

Preface: I'm know that in most cases using a volatile field won't yield any measurable performance penalty, but this question is more theoretical and targeted towards a design with an extremly high corrency support. 前言:我知道在大多数情况下,使用易失性字段不会产生任何可衡量的性能损失,但是这个问题更具理论性,并且针对具有极高一致性支持的设计。

I've got a field that is a List<Something> which is filled after constrution. 我有一个字段,它是构造后填充的List<Something> To save some performance I would like to convert the List into a read only Map. 为了节省性能,我想将List转换为只读Map。 Doing so at any point requires at least a volatile Map field so make changes visible for all threads. 这样做在任何时候都至少需要一个易失的Map字段,以便使所有线程可见更改。

I was thinking of doing the following: 我正在考虑执行以下操作:

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

This could cause multiple threads to generate the map even if they enter the get block in a serialized way. 即使它们以串行方式进入get块,这也可能导致多个线程生成映射。 This would be no big issue, if threads work on different identical instances of the map. 如果线程在映射的不同相同实例上工作,这将不是什么大问题。 What worries me more is: 更让我担心的是:

Is it possible that one thread assigns the new temp map to the map field, and then a second thread sees that map!=null and therefore accesses the map field without generating a new one, but to my suprise finds that the map is empty, because the put operations where not yet pushed to some shared memory area? 是否有可能一个线程将新的临时映射分配给map字段,然后另一个线程看到该map!=null并因此访问map字段而不生成新的临时映射,但令我惊讶的是发现该映射为空,因为put操作尚未推送到某些共享内存区域?

Answers to comments: 评论答案:

  • The threads only modify the temporary map after that it is read only. 线程仅在只读后修改临时映射。
  • I must convert a List to a Map because of some speical JAXB setup which doesn't make it feasable to have a Map to begin with. 由于某些特殊的JAXB设置,我无法将List转换为Map,这使得从一开始就没有Map是可行的。

Is it possible that one thread assigns the new temp map to the map field, and then a second thread sees that map!=null and therefore accesses the map field without generating a new one, but to my suprise finds that the map is empty, because the put operations where not yet pushed to some shared memory area? 是否有可能一个线程将新的临时映射分配给map字段,然后另一个线程看到该map!=null并因此访问map字段而不生成新的临时映射,但令我惊讶的是发现该映射为空,因为put操作尚未推送到某些共享内存区域?

Yes, this is absolutely possible; 是的,这绝对有可能; for example, an optimizing compiler could actually completely get rid of the local temp variable, and just use the map field the whole time, provided it restored map to null in the case of an exception. 例如,一个优化的编译器实际上可以完全摆脱局部temp变量,并且只要在异常情况下将map恢复为null ,就可以一直使用map字段。

Similarly, a thread could also see a non-null, non-empty map that is nonetheless not fully populated. 同样,线程也可能会看到仍然未完全填充的非空,非空map And unless your Map class is carefully designed to allow simultaneous reads and writes (or uses synchronized to avoid the issue), you could also get bizarre behavior if one thread is calling its get method while another is calling its put . 并且,除非您的Map类经过精心设计以允许同时进行读写(或使用synchronized以避免发生问题),否则,如果一个线程调用其get方法而另一个线程调用put ,则您也可能会得到奇怪的行为。

Can you create your Map in the ctor and declare it final? 您可以在ctor中创建Map并将其声明为final吗? Provided you don't leak the map so others can modify it, that should suffice to make your get() safely sharable by multiple threads. 前提是您不泄漏地图,以便其他人可以对其进行修改,这足以使您的get()安全地被多个线程共享。

In addition to the visibility concerns previously mentioned, there is another problem with the original code, viz. 除了前面提到的可见性问题外,原始代码还有另一个问题,即。 it can throw a NullPointerException here: 它可以在这里抛出NullPointerException:

return this.map.get(key)

Which is counter-intuitive, but that is what you can expect from incorrectly synchronized code. 这是违反直觉的,但这是您从错误同步的代码中可以期望的。

Sample code to prevent this: 防止这种情况的示例代码:

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

    temp = new ImmutableMap(getList());
    this.map = temp;
}
return temp.get(key);

When you really in doubt whether an other thread could read an "half completed" map (I don't think so, but never say never ;-), you may try this. 当您真的怀疑其他线程是否可以读取“半完成”映射(我认为不是,但从不说永不;-)时,可以尝试使用此方法。

map is null or complete 地图为空或完整

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

Or does someone see a new introduced problem ? 还是有人看到一个新引入的问题?

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

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