Visibility Guarantee

387 Views Asked by At

I have read a few explanations of section 16.3 "Initialization Safety" from JCIP and am still not clear. The section states that

"Further, any variables that can be reached through a final field of a properly constructed object (such as the elements of a final array or the contents of a HashMap referenced by a final field) are also guaranteed to be visible to other threads."

So if I had the following mutable object:

public final class Container{
    private String name;
    private int cupsWon;
    private double netWorth;

        public Container( String name, int cupsWon, double netWorth ){
             this.name = name;
             this.cupsWon = cupsWon;
             this.netWorth = netWorth;
        }

    //NO Setters
    //Getters
}

Then, Thread 1 creates it as following and passes c to Thread2.

final Container c = new Container("Ted Dibiasi", 10, 1000000);

Thread2 (not concurrently, lets say after 1 ms), reads values of c, is it possible that Thread2 will ever see

c.name=null or
c.cupswon=0 or worst of all, 
c.netWorth=0.0?

Cheers

UPDATE

I noticed there was some confusion about the class having getters. I am updating the source code, hopefully this will be clear. Thanks all for taking a look.

public final class Container{

    private String name;
    private int cupsWon;
    private double netWorth;

    public Container( String name, int cupsWon, double netWorth ){
        this.name = name;
        this.cupsWon = cupsWon;
        this.netWorth = netWorth;
    }

    public final String getName(){
        return name;
    }

    public final int getCupsWon(){
        return cupsWon;
    }

    public final double getNetWorth(){
        return netWorth;
    }

}

//----------

public final class Producer{

    private final Client client;

    public Producer( Client client ){
         this.client = client;
    }

    //Thread1 call produce()   
    public final void produce( ){
        final Container c = new Container("Ted Dibiasi", 10, 1000000);
        client.update( c );
    }

}

//----

public final class Client{

     private Container c;
     //private volatile Container c;       

     public final void update( Container c ){
          this.c = c;
     }

     //Thread2 calls consume().
     public final void consume( ){
          String name = c.getName();
          int cupsWon = c.getCupsWon();
          double netWorth = c.getNetWorth();           
     }

 }

My questions are:

a) When Thread2 calls consume(), can name, cupsWon, netWorth ever be null, 0, or 0.0? My thinking was that it CAN because since the fields in Container class are not final, there is no visibility guarantee.

b) However, then I read section 16.3 and the bit about "variables that can be reached through a final field of a properly constructed object", does this mean that because the instance of Container c is declared final, we DO have visibility guarantee in consume()?

final Container c = new Container("Ted Dibiasi", 10, 1000000);

c) Declaring reference to Container in Client class as volatile wont solve the visibility issue of the fields as it pertains to the reference.

3

There are 3 best solutions below

4
Denis Iskhakov On BEST ANSWER
final Container c = new Container("Ted Dibiasi", 10, 1000000);

If c here is the final field in Thread1 and not a local variable then a quote from Java Language Specification applies to this final field c:

An object is considered to be completely initialized when its constructor finishes. A thread that can only see a reference to an object after that object has been completely initialized is guaranteed to see the correctly initialized values for that object's final fields.

The usage model for final fields is a simple one: Set the final fields for an object in that object's constructor; and do not write a reference to the object being constructed in a place where another thread can see it before the object's constructor is finished. If this is followed, then when the object is seen by another thread, that thread will always see the correctly constructed version of that object's final fields. It will also see versions of any object or array referenced by those final fields that are at least as up-to-date as the final fields are.

Though the wording is vague here, I think that "correctly initialized value" and "up-to-date as the final field" means that if you pass the c to the Thread2 outside the Thread1 constructor, the Thread2 will always see a fully constructed Container instance with its fields initialized.

0
ZhongYu On

Programmers usually do not need to worry about this issue. It is only a problem if an object is "published unsafely", for example, the object is assigned to a non-volatile static field by Thread-1, and Thread-2 retrieves the object from reading the non-volatile filed. However, this is rarely the case; objects are passed between threads almost always with some memory barrier. For example, you don't need to worry about visibility when you pass the object to a ThreadPoolExecutor.

Unsafe publication should be avoided at all cost, unless you really really need it and you know exactly what you are doing.

A class usually does not need to be designed to withstand unsafe publication, unless there's a good reason to. For example, String is designed this way, because it is used extensively in core security/access control code, and the content of a string must appear to be constant, even if some hostile programs try to sabotage it through unsafe publication.

Most classes do not need to use final field for the sake of withstanding unsafe publication.

0
sandris On

To answer your question, no, Thread2 will never see the fields of Container in an uninitialized state. The reason is that the constructor of Container runs fully before the reference Container c becomes accessible. There might be a race condition between the calls Client.update and Client.consume. Depending on the result of this race, the field c is either null, or a fully initialized Container object at the time of the call c.getName() in Client.consume. In the first case, you get a NullPointerException, in the second case, the correctly initialized value. I don't think it has anything to do with the quoted sentence from JCIP.