Java double checked locking - Strings

2019-03-05 23:11发布

问题:

Given that strings contain final field, does it mean in the context of double checked locking it is not necessary to declare them volatile? E.g.

class SomeClass{
     private String val;

     String getVal(){
           if(val == null){
                synchronized(this){
                      if(val ==null)
                           val = new String("foo");
                }
          }
     }
}

I used a string as an example, but it should work with other objects that declare some final field, correct?

回答1:

For strings you're right. A string which is declared final cannot be differed and therefore you do not need to synchronize when using it.

Thats not true for other Objects. Take this little class for example:

public class BankAccount {
    private int balance = 0;
    public void addMoney(int money) {
        balance+=money;
    }
}

When you've got a final Object of this class it doesn't mean that nobody can change the fields inside the object. You just can't assign something else to the final variable!

Conclusion: When accessing final String you don't need to synchronize, when accessing final Objects you might have to, depending on the Object itself.



回答2:

No, you still have to declare val as volatile here. The problem is that while String is immutable and thread safe, val is not. You still have a visibility problem with val itself.

To address your point about "given that String contains a final field," note that the JLS specifically says that visibility is not transitive when dealing with final fields.

Given a write w, a freeze f, an action a (that is not a read of a final field), a read r1 of the final field frozen by f, and a read r2 such that hb(w, f), hb(f, a), mc(a, r1), and dereferences(r1, r2), then when determining which values can be seen by r2, we consider hb(w, r2). (This happens-before ordering does not transitively close with other happens-before orderings.)

https://docs.oracle.com/javase/specs/jls/se8/html/jls-17.html#jls-17.5

Where a "freeze f" is how the JLS refers to the thread-safe part of final field semantics, i.e., the part that actually makes the object referenced by the field visible.

(There are cases where you can rely on transitivity with synchronizes-with and happens-before. Brian Goetz calls this 'piggy-backing' and talks about it in Java Concurrency in Practice. But it's pretty much experts only and I don't recommend it until you are an expert with the Java memory model.)

In short, declare val volatile and don't worry about saving two nanoseconds by skipping synchronization. The extra rigmarole in the code isn't worth it, and it doesn't work anyway.