Do I have reordering issue and is it due to reference escape?

Gurwinder Singh :

I have this class where I cache instances and clone them (Data is mutable) when I use them.

I wonder if I can face a reordering issue with this.

I've had a look at this answer and JLS but I am still not confident.

public class DataWrapper {
    private static final ConcurrentMap<String, DataWrapper> map = new ConcurrentHashMap<>();
    private Data data;
    private String name;

    public static DataWrapper getInstance(String name) {
        DataWrapper instance = map.get(name);
        if (instance == null) {
            instance = new DataWrapper(name);
        }
        return instance.cloneInstance();
    }

    private DataWrapper(String name) {
        this.name = name;
        this.data = loadData(name);  // A heavy method
        map.put(name, this);  // I know
    }

    private DataWrapper cloneInstance() {
        return new DataWrapper(this);
    }

    private DataWrapper(DataWrapper that) {
        this.name = that.name;
        this.data = that.data.cloneInstance();
    }
}

My thinking: The runtime can reorder the statement in constructor and publish the current DataWrapper instance (put in the map) before initializing the data object. A second thread reads the DataWrapper instance from map and sees null data field (or partially constructed).

Is this possible? If yes, Is it only due to reference escape?

If not, could you please explain me how to reason about the happens-before consistency in simpler terms?

What If I did this:

public class DataWrapper {
    ...
    public static DataWrapper getInstance(String name) {
        DataWrapper instance = map.get(name);
        if (instance == null) {
            instance = new DataWrapper(name);
            map.put(name, instance);
        }
        return instance.cloneInstance();
    }

    private DataWrapper(String name) {
        this.name = name;
        this.data = loadData(name);     // A heavy method
    }
    ...
}

Is it still prone to the same issue?

Please note that I don't mind if there are one or two extra instances created if multiple threads try to create and put the instance for the same value at the same time.

EDIT:

What if the name and data fields were final or volatile?

public class DataWrapper {
    private static final ConcurrentMap<String, DataWrapper> map = new ConcurrentHashMap<>();
    private final Data data;
    private final String name;
    ... 
    private DataWrapper(String name) {
        this.name = name;
        this.data = loadData(name);  // A heavy method
        map.put(name, this);  // I know
    }
    ...
}

Is it still unsafe? As far as I understand, the constructor initialization safety guarantees only applies if the reference hasn't escaped during initialization. I am looking for official sources that confirm this.

Rafael Winterhalter :

If you want to be spec-compliant, you cannot apply this constructor:

private DataWrapper(String name) {
  this.name = name;
  this.data = loadData(name);
  map.put(name, this);
}

As you point out, the JVM is allowed to reorder this to something like:

private DataWrapper(String name) {
  map.put(name, this);
  this.name = name;
  this.data = loadData(name);
}

When assigning a value to final field, this implies a so-called freeze action at the end of the constructor. The memory model guarantees a happens before relationship between this freeze action and any dereferenzation of the instance on which this freeze action was applied upon. This relationship does however only exist at the end of the constructor, therefore, you break this relationship. By dragging the publication out of your constructor, you can fix this realtionship.

If you want a more formal overview of this relationship, I recommend looking through this slide set. I also explained the relationship in this presentation starting at about minute 34.

Guess you like

Origin http://10.200.1.11:23101/article/api/json?id=438908&siteId=1