Search code examples
javaconcurrencysynchronizedmemory-modeljava-memory-model

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. To save some performance I would like to convert the List into a read only Map. Doing so at any point requires at least a volatile Map field so make changes visible for all threads.

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. 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?

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.

Solution

  • 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?

    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.

    Similarly, a thread could also see a non-null, non-empty map that is nonetheless not fully populated. 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.