In a Java synchronized block, are writes visible on all fields or just the synchronized variable?
Asked Answered
B

3

15

Say you have this code:

private String cachedToken;
private final Object lockObject = new Object();

....


retrieveToken(){
 synchronized(lockObject){
  if (cachedToken == null){
   cachedToken = goGetNewToken();
  }
  return cachedToken;
 }
}

Will the write to cachedToken be visible to all threads that have locked on lockObject?

Balling answered 20/5, 2013 at 15:45 Comment(4)
Yes, that's the point... But the write could happen to your CPU's l1 or l2 cache and not be flushed to main memory before another thread gains the lock.Balling
That's not true. If another thread locks on lockObject after another thread writes then leaves, the entering thread will see the write.Terefah
Note: writes to the "synchronized variable" are frequently bad. Code is synchronized on an object, not a variable. If the variable is changed midstream to reference a different object, multiple blocks synchronized on the object referenced by that variable could execute concurrently.Conveyancer
@AndyThomas-Cramer right, thats exactly why I have a separate lockObject rather than using cachedToken. I can make that field final to make sure the reference never changes.Balling
B
11

Yes. Synchronizing on lockObject establishes a Happens Before Relationship (aka sets up a memory barrier). This means that all threads that subsequently get the lock will see any changes that happened while the lock was held previously.

For what it's worth, though, your implementation of lazy initialization is flawed. This is the proper way:

private volatile String cachedToken;

retrieveToken() {
    if (cachedToken == null) {
        synchronized(lockObject) {
            if (cachedToken == null) {
                cachedToken = goGetNewToken();
            }
        }
    }
    return cachedToken
}

This way you only have to get the lock a small handful of times when Threads first start requesting it. After that the cachedToken will not be null, and you won't need to synchronize.

Boz answered 20/5, 2013 at 15:48 Comment(23)
And that memory barrier will include any field touched, not just the object that I synchronized on?Balling
@Tom McIntyre Your way of doing is actually unsafe. Look at this: cs.umd.edu/~pugh/java/memoryModel/DoubleCheckedLocking.htmlHomocercal
yes, otherwise shared-state concurrency would be even more difficult than it already is!Boz
@Homocercal ah yes, I've seen that. Right at the bottom there's a little comment that adds that making the field volatile ensures it will work since java 5. I have updated my answer with the volatile used.Boz
thanks :) Second, is my way "broken" or just inefficient? It seems that acquiring the lock every time would be expensive, but it would also be 'correct'.Balling
@Tom McIntyre Adding volatile works since java 5, but there are both pretty solutions instead of it: use enum or lazy initialization with static nested class.Homocercal
@exabrial Not inefficient but really broken. That deals with the way of the JMM works: ibm.com/developerworks/library/j-jtp03304Homocercal
@Homocercal His way isn't broken by any means. The read always happens under the same lock that is written to.Terefah
Technically the volatile isn't required here: the object in question is a String, which is immutable, and as such is treated specially by the JMM: initialization of immutable object and their fields is atomic, so there is no chance that an incompletely initializd object will be published. In general, though, use volatile.Boz
If cachedToken is used by several threads, then yes, he's code is risked. Threads could end up with a cachedToken object with fields still assigned to their respective default values (apart for finalfields that benefit since JSR-133 of Memory Barrier). It's worth to avoid this style of pattern.Homocercal
@Homocercal That isn't true for immutable objects like String. But if the read occurs in the same lock the write occurs in then, non immutable objects are safe from this type of publication. Hence the OPs example isn't broken.Terefah
@John Vint That was I precised, final field are concerning by memory barrier. And I would like to precise than we could make an immutable object without containing any final fields.Homocercal
@Homocercal I updated my comment to account for non-immutable objectsTerefah
@John Vint Yes, we could always find specific case when this pattern applies. But why would we need to bother with some specific conditions before ensuring a correct behavior? I really prefer to use the static nested class approach to deal with lazy initialization, and it works with ALL cases. You, John Vint, indeed, could use this pattern since it really sounds you have the knowledge of its potential drawbacks. But what about so many developers who ignore them? I prefer to tell them: there is a better way to do, rather than telling them: yes yes, that works, without explaining the "risks".Homocercal
@Homocercal I agree I would absolutely use that too. I was just correcting you saying that the OPs example was incorrect.Terefah
@Homocercal But there are no risks in the OPs example. I would argue there are more risks with DCL. I would advise to use his pattern over anything other then the static nested class approach.Terefah
@John Vint There are both code snippets: the "official" from the question, and the solution brought by this post (using DCL). I was talked about the latter.Homocercal
@John Vint By the way, in the official snippet, we can see "...". What about if there is a public method here, reached by some others thread simultanously and accessing the shared object. It may end up with an inconsistent one => Still the same issue than DCL.Homocercal
@Homocercal He doesnt use DCL. He checks once in the synchronized blockTerefah
@John Vint Checking once or ten times doesn't modify the issue to solve.Homocercal
What issue? @exabrial Not inefficient but really broken. This issue, that is what I am talking about.Terefah
@John Vint Yes, sorry you're right, this issue only happened with DCL. To sum up: offical snippet: inefficient, this answer's snippet: broken ;) You was talking about official snippet initially; I was talking about this one :)Homocercal
@Homocercal Ok agreed! I hate DCL myself :)Terefah
H
8

Of course, synchronizeensure two things:

  • Atomicity
  • Memory barrier (what you expect in your case) on the entire object

Whereas for instance, volatileensure memory barrier but doesn't handle atomicity.

Homocercal answered 20/5, 2013 at 15:51 Comment(6)
apart from reads and writes to longs or doubles, where volatile DOES ensure atomicity.Boz
@Tom McIntyre Yes, but it's not due to the volatilekeyword itself.Homocercal
how do you mean 'not due to the volatile keyword itself'?Boz
@Tom It's due mostly by the way of compiler works with large primitive like long, double etc.. Of course, placing volatile on a int acts like an AtomicInteger but it's better to forget this specific case and always avoid volatile for atomicity needs rather than happens-before needs.Homocercal
volatile and AtomicXXX have different use cases. And volatile does ensure atomic reads and writes of doubles and longs. It has nothing to do with the compiler.Boz
@Tom Mcintyre You're right, but it's a special case for volatile to simplify programmers habits and not its essence itself to be focused on atomicity. For the purposes of the Java programming language memory model, a single write to a non-volatile long or double value is treated as two separate writes: one to each 32-bit half. This can result in a situation where a thread sees the first 32 bits of a 64 bit value from one write, and the second 32 bits from another write. HOWEVER, writes and reads of volatile long and double values are always atomic.Homocercal
M
1

Writes will be visible on all fields.

In essence, releasing a lock forces a flush of all writes from working memory employed by the thread, and acquiring a lock forces a (re)load of the values of accessible fields. While lock actions provide exclusion only for the operations performed within a synchronized method or block, these memory effects are defined to cover all fields used by the thread performing the action

Source: Concurrent Programming in Java, Doug Lea http://gee.cs.oswego.edu/dl/cpj/jmm.html

Mezzorilievo answered 15/9, 2021 at 21:53 Comment(1)
Years later, this is still one of the best questions I've asked on SO and the answers coming in continue to teach me new things. Thank you!Balling

© 2022 - 2024 — McMap. All rights reserved.