Win a copy of Cross-Platform Desktop Applications: Using Node, Electron, and NW.js this week in the JavaScript forum!
  • Post Reply Bookmark Topic Watch Topic
  • New Topic

The Memory Model and Fences  RSS feed

 
Stevens Miller
Bartender
Posts: 1444
30
C++ Java Netbeans IDE Windows
  • Mark post as helpful
  • send pies
  • Quote
  • Report post to moderator
As I understand it, a common idiom for the consumer-side of a producer/consumer pair of threads might look like this:


Now, owing to caching and other optimizations, the value in "producerWroteThisReference" in the consumer's thread would not necessarily be whatever the producer had most recently stored in "producerWroteThisReference," were it not for the memroy-fence created by synchronization (assuming, that is, that the producer synchronizes the block of code wherein it assigns a value to "producerWroteThisReference," and that it synchronized it on "obj").

But, thanks to the fence created by synchronization, the value in "producerWroteThisReference" is sure to be what was previously written into it by the producer thread.

However, suppose the definition of MyObject is this:

And, suppose the producer also wrote a value to the "x" member of the instance referred to by "producerWroteThisReference." What guarantees that the consumer thread will see what was written to that member? Does the JVM know how to descend into the structure of MyObject? Or, must the code on the producer thread that assigns a value to the "x" member also be within the synchronized block?

For the reference itself, it's easy to see how the memory fence makes sure the consumer sees what the producer stored. But for the member values (particulary for objects that have complex internal structures, with, say, nested objects, arrays, and so on), how can I be sure one thread's values are available to the other thread? (Pointers to existing discussions/pages on this gratefully accepted!)
 
Greg Charles
Sheriff
Posts: 3014
12
Firefox Browser IntelliJ IDE Java Mac Ruby
  • Mark post as helpful
  • send pies
  • Quote
  • Report post to moderator
Thread synchronization is done by monitor, so you're probably just confusing yourself by asking if the JVM knows how to descend into objects. The code: tells the thread to try to obtain the monitor for the instance pointed to by "obj". If it can, it executes the code in the block. If it can't, it automatically goes into a waiting state until it can obtain the monitor. I like to think of the monitor as a beach ball, and each object (i.e., each instance) has its own beach ball. It gives the beach ball to any thread that asks for it, but it can't give it to two threads at once. It has to get the ball back from one thread before handing it out to another.

Now the synchronized block of code can really be anything. Generally speaking, it will work with the object whose monitor it has, but that's not a mandatory. Any block can be synchronized on any object, and all that the JVM guarantees is that two threads cannot simultaneously enter blocks of code synchronized on the same object. If you synchronize on obj, and obj has a field ... not something immutable like an int, but some object type say ... then you absolutely have to be careful to not to modify it directly through its reference in an unsynchronized block, or a block synchronized on something other than obj.
 
Stevens Miller
Bartender
Posts: 1444
30
C++ Java Netbeans IDE Windows
  • Mark post as helpful
  • send pies
  • Quote
  • Report post to moderator
Greg Charles wrote:If you synchronize on obj, and obj has a field ... not something immutable like an int, but some object type say ... then you absolutely have to be careful to not to modify it directly through its reference in an unsynchronized block, or a block synchronized on something other than obj.

Thanks. Does that mean the memory fence only guarantees consistent memory visibility across threads for the fields in the object used in the synchronize statement?
 
Greg Charles
Sheriff
Posts: 3014
12
Firefox Browser IntelliJ IDE Java Mac Ruby
  • Mark post as helpful
  • send pies
  • Quote
  • Report post to moderator
No, it means that synchronization only controls access to the blocks of code ... what used to be called critical sections back when I studied CS. There's no guarantee of exclusive access to objects whatsoever. It's up to you, the coder, to make sure that data structures you are worried about are protected from unsynchronized access, like by making them private members of a class, not exposing them outside the methods of that class, and having all methods of the class that mutate those data structures be correctly synchronized.
 
Stevens Miller
Bartender
Posts: 1444
30
C++ Java Netbeans IDE Windows
  • Mark post as helpful
  • send pies
  • Quote
  • Report post to moderator
Greg Charles wrote:No, it means that synchronization only controls access to the blocks of code ... what used to be called critical sections back when I studied CS. There's no guarantee of exclusive access to objects whatsoever. It's up to you, the coder, to make sure that data structures you are worried about are protected from unsynchronized access, like by making them private members of a class, not exposing them outside the methods of that class, and having all methods of the class that mutate those data structures be correctly synchronized.


I think you're mistaken about that, Greg. If the only access to a piece of data is via two synchronized methods in the same class, only one method can get at that data at a time. Likewise, the idiom I quoted above can be used to limit access to data if the only access is inside blocks synchronized on the same object. What I'm trying to get a handle on is how synchronized access to data guarantees consistent memory visibility between threads. The section on that in "Effective Java, 2d ed.," is a bit murky to me.
 
Steve Luke
Bartender
Posts: 4181
22
IntelliJ IDE Java Python
  • Mark post as helpful
  • send pies
  • Quote
  • Report post to moderator
Stevens Miller wrote:
Greg Charles wrote:No, it means that synchronization only controls access to the blocks of code ... what used to be called critical sections back when I studied CS. There's no guarantee of exclusive access to objects whatsoever. It's up to you, the coder, to make sure that data structures you are worried about are protected from unsynchronized access, like by making them private members of a class, not exposing them outside the methods of that class, and having all methods of the class that mutate those data structures be correctly synchronized.


I think you're mistaken about that, Greg. If the only access to a piece of data is via two synchronized methods in the same class, only one method can get at that data at a time. Likewise, the idiom I quoted above can be used to limit access to data if the only access is inside blocks synchronized on the same object.

I think you both just said the same thing, except Greg stated it as a warning: "You, the coder must make sure the methods/blocks are synchronized properly", and you simply stated it "If you only access it through synchronized blocks or methods it will be okay".

Stevens Miller wrote: What I'm trying to get a handle on is how synchronized access to data guarantees consistent memory visibility between threads. The section on that in "Effective Java, 2d ed.," is a bit murky to me.

The 'how' is an implementation detail, and so isn't specified. The 'what' and 'why' are specified. The end of a synchronization block is an 'unlock action', and the start of a synchronization block is a 'lock action.' There is a 'synchronizes-with' border between an unlock action and subsequent lock actions on the same monitor. This synchronization border defines a 'happens before' relationship between the end of one synchronized block and the start of another synchronized block (if they use the same Object).

In addition to this happens before relationship, there is also a happens before relationship between the code in a synchronization block and the the end of the synchronization block because they follow intra-thread programming order. And happens before relationships are transitive. So the code that is in a synchronized block happens before the end of the synchronized block in one thread, and the end of the synchronized block in one thread comes before the start of the synchronized block in another thread, therefor the code in the synchronized block in one thread happens before the code in synchronized block in another thread. This happens before relationship is important, because it means that the writes in the first thread must have happened before, and therefore be visible to, the reads in the second thread.

But that is as far as the specification goes. The rules for happens before must be met, so the writes in one thread must be visible to reads in a second thread when they are properly synchronized, but there is no definition on how that may happen. It may happen that the end of a synchronized block forces a flush of memory from the local cache to the heap and that the start of a synchronized block forces re-reading variables used in the block to be retrieved from the heap rather than using the local cache. But there may be any number of 'hows'.
 
Stevens Miller
Bartender
Posts: 1444
30
C++ Java Netbeans IDE Windows
  • Mark post as helpful
  • send pies
  • Quote
  • Report post to moderator
Steve Luke wrote:The rules for happens before must be met, so the writes in one thread must be visible to reads in a second thread when they are properly synchronized, but there is no definition on how that may happen. It may happen that the end of a synchronized block forces a flush of memory from the local cache to the heap and that the start of a synchronized block forces re-reading variables used in the block to be retrieved from the heap rather than using the local cache. But there may be any number of 'hows'.


Yeah, I see the distinction you are making, Steve. The "how" actually isn't important to me. What I am really trying to understand is the "what" of memory visibility. Is it simply the case that, if a variable is assigned its value anywhere inside a block synchronized on obj, that code executed later inside a block also synchronized on obj is sure to see that value?

In particular, suppose I have code that fills a large buffer. If I pass a reference to that buffer to another thread inside a synchronized block, I am confident that the reference will be visible to the other thread (that is, if the producer thread writes the reference value to a variable inside a synchronized block, and the consumer thread reads the value from that same variable in a happens-after synchronized block, I know the consumer will see the value written by the producer). But what about the buffer's contents? For those contents to be visible (for sure, that is), would it be necessary for those contents also to be written within the producer's happens-before synchronized block?
 
Chris Hurst
Ranch Hand
Posts: 443
3
C++ Eclipse IDE Java
  • Likes 1
  • Mark post as helpful
  • send pies
  • Quote
  • Report post to moderator
Yeah, I see the distinction you are making, Steve. The "how" actually isn't important to me. What I am really trying to understand is the "what" of memory visibility. Is it simply the case that, if a variable is assigned its value anywhere inside a block synchronized on obj, that code executed later inside a block also synchronized on obj is sure to see that value?


All writes by a given thread prior to the lock release are visible to all reads of another thread post lock acquisition (Must be same lock or there is a gotcha). Modifications do not have to be in the block in terms of visibility ie writes prior to entry will be published correctly on block exit.

For simplicity assume all writes are potentially cached prior to synchronized block exit (includes those prior to block entry) so the JVM would (actually could) insert an instruction to flush the write cache releasing all cached thread writes, on lock acquistion the JVM would add an instruction to flush the read cache, these instructions would be your memory barrier fences.

// flush all reads READ MEMORY BARRIER
// Strictly: all writes that occurred anywhere in another thread that synchronized on 'object' prior to that thread releasing the lock on object are now guaranteed to be
// visible to this thread when it acquires the lock
synchronized (object)
{


}
// flush all writes WRITE MEMORY BARRIER
// Strcitly: all writes that happened in this thread prior to this point are visible to another thread that acquires this object lock in future.

The reality is more complex (eg code reordering and optimisations ie the JVM only uses fences if it has to) and the strict definition is 'happens before ordering' as described in the java memory model ie always program to this not what I said ;-)

In answer to your first question if MyObject.x was changed by a mutator thread then you can think of this issue as that change to x would be in the write cache so we have to ensure that its written to main memory before the other thread uses it (write barrrier) but also the other thread must ensure that it doesn't have a cached version of x its using so it must also flush its read cache (read barrier). There is nothing to look up or descend its very simple, it works by changes not objects as such.
 
Stevens Miller
Bartender
Posts: 1444
30
C++ Java Netbeans IDE Windows
  • Mark post as helpful
  • send pies
  • Quote
  • Report post to moderator
Chris Hurst wrote:All writes by a given thread prior to the lock release are visible to all reads of another thread post lock acquisition (Must be same lock or there is a gotcha). Modifications do not have to be in the block in terms of visibility ie writes prior to entry will be published correctly on block exit.

Thanks, Chris! That speaks directly to what I am (trying to be ) asking. Now can you point me to a passage in the JLS that says this? It's exactly what I'm hoping for; just want to read how it's expressed in formal terms.
 
Chris Hurst
Ranch Hand
Posts: 443
3
C++ Eclipse IDE Java
  • Likes 1
  • Mark post as helpful
  • send pies
  • Quote
  • Report post to moderator
This is the document that explains happens before ordering (see page 12 for notes on synchronization) ...

JSR133 - Java Memory Model

If your more interested in the topic in general you might want to start somewhere like here and follow the links ...

Memory ordering

... to see the issue at a CPU level , Java cleverly abstracts away from this to present us with a consistent memory model so we don't need to worry about what is going on at a CPU / OS level.
 
Stevens Miller
Bartender
Posts: 1444
30
C++ Java Netbeans IDE Windows
  • Mark post as helpful
  • send pies
  • Quote
  • Report post to moderator


Thanks, Chris! That is great stuff.

Do you know of any code fragments that reliably demonstrate incoherent memory visibility between Java threads? My simple experiments (wherein one thread simply assigns a value to a static int, then another thread reads it) always result in the consumer side reading back what was written by the producer side, whether I use synchronization or not. It would be great if I could force a visibility failure on the consumer side when synchronization is not used, so I could create some test cases that show synchronization's effect. There are quite a few examples out there of code that cannot be trusted (because of the lack of synchronization), but none of them reliably demonstrates the problem (which, I suppose, is part of why this can be such a hard thing to debug if one neglects to synchronize properly).
 
Stevens Miller
Bartender
Posts: 1444
30
C++ Java Netbeans IDE Windows
  • Mark post as helpful
  • send pies
  • Quote
  • Report post to moderator
Heh, doing a little more searching for such examples myself, I found some pretty persuasive claims that they are really not available. In Concurrent Programming in Java, Doug Lea says this:
...the rules do not require visibility failures across threads, they merely allow these failures to occur. This is one aspect of the fact that not using synchronization in multithreaded code doesn't guarantee safety violations, it just allows them. On most current JVM implementations and platforms, even those employing multiple processors, detectable visibility failures rarely occur. The use of common caches across threads sharing a CPU, the lack of aggressive compiler-based optimizations, and the presence of strong cache consistency hardware often cause values to act as if they propagate immediately among threads. This makes testing for freedom from visibility-based errors impractical, since such errors might occur extremely rarely, or only on platforms you do not have access to, or only on those that have not even been built yet.

Pretty depressing stuff, since it means you can't really test for unsynchronized code. Instead, you have to apply guidelines that help you find and correct it.

Regarding the original question, here's what the same reference has to say:
...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.[emphasis added]

Which confirms what you said, Chris.

Thanks again. Marking this item "Resolved."
 
  • Post Reply Bookmark Topic Watch Topic
  • New Topic
Boost this thread!