Stefan Dollase
Stefan Dollase

Reputation: 4620

Is the safe publishing of final instance-variables transitive for non-final secondary references?

I know that final instance-variables are published safely to all threads, after the constructor is finished. However, I wonder whether this is still safe, if the final instance-variable contains a reference to an object that contains a non-final instance-variable. This secondary, non-final instance-variable is never changed after the constructor is done. Consider the following example:

public class NonFinalImmutable {
    private Iterable<String> list = Collections.unmodifiableList(Arrays
            .asList("foo", "bar", "foobar"));

    public Iterable<String> getList() {
        return list;
    }
}

public class FinalImmutable {
    private final NonFinalImmutable reference;
    private final String[] array;

    public FinalImmutable(NonFinalImmutable reference,
            String... arrayEntries) {
        this.reference = reference;
        this.array = arrayEntries;
    }

    public NonFinalImmutable getReference() {
        return reference;
    }

    public String[] getArray() {
        return array;
    }
}

private void execute() {
    new Thread() {
        @Override
        public void run() {
            useLater(construct());
        }
    }.start();
}

private FinalImmutable construct() {
    return new FinalImmutable(new NonFinalImmutable(), "asdf", "jklö");
}

private void useLater(FinalImmutable finalImmutable) {
    new Thread() {
        @Override
        public void run() {
            for (String s : finalImmutable.getReference().getList()) {
                System.out.println(s);
            }
            System.out.println();
            for (String s : finalImmutable.getArray()) {
                System.out.println(s);
            }
        }
    }.start();
}

Is it safe to use the contents of the instance-variables FinalImmutable.reference and FinalImmutable.array in another thread even though they contain non-final instance-variables?

Upvotes: 4

Views: 196

Answers (1)

John Vint
John Vint

Reputation: 40256

Yes, there is a freeze-action which occurs when assigning final fields. You should read Aleksey Shipilëv's blog it's really useful. He discusses the freeze action semantics in a 2014 blog entry

And here is how it is formally specified. Notice that w may not be the write of final field, and r2 is not the read of the final field. What really matters is that the subchain containing freeze action F, some action a, and r1 which reads the final field — all together make r2 observe w.

Notice two new orders, dereference order, and memory

In the blog he proves that a write of final field happens before some action which in turn happens before a subsequent non-final field read r2.

Also in your example, since you first construct the a non-shared NonFinalImmutable the final assignment should freeze the writes occurred prior. If the NonFinalImmutable was accessible outside, all bets are off.

Upvotes: 3

Related Questions