thread working memory and synchronized keyword


G

gubd2005

Hi folks.

I have a question about the example code at the bottom of this message
(excuse the omission of getters/setters).

Basically I have method in the class Test that starts a new thread (an
instance of the class X). Lets say thread #1 is the thread that calls
the method in Test, and thread #2 is the newly created thread. Thread
#1 waits until thread #2 reaches a checkpoint. Thread #2, meanwhile,
writes a value (writeValue) and then notifies thread #1 that it has
reached the checkpoint. Thread #1, after passing the checkpoint, tries
outputting the writeValue that thread #2 wrote.

Here's my question: Is it necessary to put the write of writeValue in
class X, and the read of writeValue in class Test, into synchronized
blocks? The discussion in the 2nd edition of the Java Language Spec
with regards to thread working memory and synchronization implies that
the answer is YES, they must be surrounded by synchronized blocks.
Otherwise, writeValue written by thread #2 can remain in its working
memory, and thread #1 may only see the initial value of writeValue.
This is despite the guarantee that the "checkpoint" provides that
thread #2 will have written writeValue before thread #1 tries reading
it.

Another point of confusion for me is the disappearance of the
discussion of thread working memory from the 3rd edition of the Java
Language Spec. I haven't gone through the details of the 3rd edition
spec, but is the concept of working memory still there, just disguised
in different terms? A virtually identical discussion of thread working
memory is in the latest edition of the VM spec (2nd edition), so was
the omission just to clean up the duplication of the discussion between
the two specs? Or was there another reason?

Thanks.

Bob.

public class X extends Thread {
public boolean checkPoint = false;
public int writeValue = 0;
public void run() {

// should the following line be in a synchronized block?
writeValue = 500;

synchronized (this) {
checkPoint = true;
this.notify();
}
for(;;) { // do some stuff forever without the thread ever dying }
}
}

public class Test {

public void m() throws InterruptedException {

X x = new X();
x.start();
synchronized(x) {
if (!x.checkPoint) {
x.wait();
}
}
// should the following line be in a synchronized block?
System.out.println("x.writeValue == "+x.writeValue);
}
}
 
Ad

Advertisements

T

Thomas Hawtin

Another point of confusion for me is the disappearance of the
discussion of thread working memory from the 3rd edition of the Java
Language Spec. I haven't gone through the details of the 3rd edition
spec, but is the concept of working memory still there, just disguised
in different terms? A virtually identical discussion of thread working
memory is in the latest edition of the VM spec (2nd edition), so was
the omission just to clean up the duplication of the discussion between
the two specs? Or was there another reason?

The happens-before concept replaces the concept of local memory. Local
memory is appealing because we can think of it in terms registers, cache
and write buffers. However compiler optimisations and I believe certain
hardware configurations make it unworkable.
public class X extends Thread {
public boolean checkPoint = false;
public int writeValue = 0;
public void run() {

// should the following line be in a synchronized block?
writeValue = 500;

synchronized (this) {
checkPoint = true;
this.notify();
}
for(;;) { // do some stuff forever without the thread ever dying }

The for (;;); technically means that the main thread may never make
progress.
}
}

public class Test {

public void m() throws InterruptedException {

X x = new X();
x.start();
synchronized(x) {
if (!x.checkPoint) {
x.wait();

The code is clearly not thread-safe as the wait can spuriously wake-up.

Replace the if with a while and it becomes different. We can see the
Test synchronized block does not exit until after the X one does.
}
}
// should the following line be in a synchronized block?
System.out.println("x.writeValue == "+x.writeValue);
}
}

All operations in a single thread are ordered, as are the locking
operations. So we have the write happens-before the X lock
happens-before the last Test locks (either entering the synchronized
block or exiting wait) happens-before the read. Therefore write
happens-before read.

Under the old (broken) model the write would be flushed to main memory
at the end of the first synchronized block and cached read value would
be dirty at the Test locks.


At least, that's how I think it works.

You wouldn't be able to do much useful if it didn't work.

I think the best advice is to keep to the common idioms where possible.
Like the while loop.

Tom Hawtin
 
C

Chris Uppal

Another point of confusion for me is the disappearance of the
discussion of thread working memory from the 3rd edition of the Java
Language Spec. I haven't gone through the details of the 3rd edition
spec, but is the concept of working memory still there, just disguised
in different terms?

No, it's different. The old spec was discovered to be buggy and has been
completely re-designed.

The main feature -- that synchonisation (and "volatile") is the only correct
way to pass data between threads -- is the same as ever, but the rules that
combine to make that true have been replaced completely.

I believe (but may be wrong) that the only change that affects working Java
programmers (as opposed to JVM implementors and language lawers) are to do with
threadsafe intialisation of instance fields. I wish I could remember the
details -- something to do with overloading the meaning of "final" to make the
intialisation of such fields be a "synchronisaton barrier" (in the old
terminology) is as close as I can get...

-- chris
 
J

jan V

I believe (but may be wrong) that the only change that affects working
Java
programmers (as opposed to JVM implementors and language lawers)

I've always questioned the need for language lawyer-style knowledge of a
language. Sure you need to know how everything works, the boundaries, the
limits, etc.. but only for language usage scenarios which make software
engineering sense. And such scenarios are just a small subset of what the
compiler will swallow without complaining.

The core rule of coding style is that you want your programs to be readable
(/maintainable) by other programmers. So your main "dialogue" when crafting
your code is not with the machine, but is with imaginary human readers. That
rule of thumb, if followed, means one should always try to write clear,
easily understood constructs... which have the least possible "trickiness".

To get back to the OP's thread, this means I don't need to know about the
underlying magic behind thread synchronization. I embraced Java in 1996 for
hiding, among many other things, such unnecessary low-level side-topics.
 
T

Thomas Hawtin

Chris said:
The main feature -- that synchonisation (and "volatile") is the only correct
way to pass data between threads -- is the same as ever, but the rules that
combine to make that true have been replaced completely.

If it isn't crucial that the other thread picks the value up (say for a
cache or checks that you'll make later classically) then you can use
objects using the final field semantics (or primitive values).

Tom Hawtin
 
T

Thomas Hawtin

jan said:
I've always questioned the need for language lawyer-style knowledge of a
language. Sure you need to know how everything works, the boundaries, the
limits, etc.. but only for language usage scenarios which make software
engineering sense. And such scenarios are just a small subset of what the
compiler will swallow without complaining.

You need to know where those limits are so you don't accidentally over
step them. See the Java Puzzlers book for lots of examples of confusing
behaviour (although many are just corner cases).

http://www.amazon.co.uk/exec/obidos/ASIN/032133678X/
To get back to the OP's thread, this means I don't need to know about the
underlying magic behind thread synchronization. I embraced Java in 1996 for
hiding, among many other things, such unnecessary low-level side-topics.

A large proportion of Java programmers appear to be under the impression
that one thing happens after another. That is a too simplistic model.

The worst, but not unexpected, case is code that works today, passes
extensive tests but screws up next week.

The original poster was not querying some obscure behaviour. The example
program (with while loop inserted) was the canonical pass-information-
from-one-thread-to-another problem. The while loop is only strictly
speaking necessary in this stripped-to-the-bone example because of a
'clarification' in 5.0. It's not the first piece of code to fall foul.
I've found it in java.awt.EventQueue, a commercial program and some
example code. (In practice Sun's HotSpot is fine with this code for
backward compatibility reasons.)

Tom Hawtin
 
Ad

Advertisements

J

John C. Bollinger

I have a question about the example code at the bottom of this message
(excuse the omission of getters/setters).
[...]

Here's my question: Is it necessary to put the write of writeValue in
class X, and the read of writeValue in class Test, into synchronized
blocks? The discussion in the 2nd edition of the Java Language Spec
with regards to thread working memory and synchronization implies that
the answer is YES, they must be surrounded by synchronized blocks.

No. Whether under the old memory model or the new, all writes that
happen before a thread exists a synchronized block are visible to other
threads after that thread exits the block, whether the update occurred
inside the block or before it. (The new model defines the term "happens
before" in detail, but the general idea is still applicable to earlier
VMs, I think.) All reads that happen after the entry to a synchronized
block will in fact see all values visible to the thread before entry to
the block, whether the read itself occurs inside the block or after it.
If you consider the question at the bytecode level then that's the only
behavior that makes sense: monitorenter and monitorexit bytecodes are
not required to be paired, therefore their behavior must be specified
independent of each other, and the concept of a "synchronized block" is
foreign.

[...]
// should the following line be in a synchronized block?
writeValue = 500;

synchronized (this) {
checkPoint = true;
this.notify();
}

[... a different method ...]
 
C

Chris Uppal

Thomas said:
If it isn't crucial that the other thread picks the value up (say for a
cache or checks that you'll make later classically) then you can use
objects using the final field semantics (or primitive values).

Sorry, I'm not sure what you are getting at here ?

-- chris
 
C

Chris Uppal

jan said:
I've always questioned the need for language lawyer-style knowledge of a
language. Sure you need to know how everything works, the boundaries, the
limits, etc.. but only for language usage scenarios which make software
engineering sense. And such scenarios are just a small subset of what the
compiler will swallow without complaining.

The problem is that the language-lawyer-level details (LLLDs hereinafter)
become relevant in "ordinary" situations too. I'll agree that one can be a
good programmer without LL-style deep knowledge of the language, but I think
that any team needs at least one member who is conversant with the LLLDs.
Actually this goes for systems as well as languages.

Any realistic system is complicated -- there are the details of the application
design itself (which might be trivial in a "hello world!" application), there
are the details of how the code relates to the language semantics, there are
the details of how those semantics are implemented, there are the details of
the OS and/or network, etc. As I say, in any realistic application (including
tiny ones) at least /one/ of those must be complicated, and at least /one/ of
those must be subtle. A genuine understanding of "what we are doing" must
therefore include and understanding of that complexity and subtlety.

In practice, in many cases, a major source of that complexity and subtlety is
the programming language itself. Obviously there are differences between
languages in this respect. C++, for instance, is so difficult that I don't
think there are many C++ programmers in existence (most people actually are
writing in a simplified approximation to C++ that -- whether or not they
realise it -- does not have the same semantics as the language recognised by
their compiler). The only language of which I am aware that even /looks/ as if
it might be completely transparent is FORTH -- I don't know whether it really
/is/ transparent (if forced to bet, I would bet against it), but at least it
looks it to an outsider. Java is certainly /not/ transparent -- as the many
posts here looking for help with problems that hinge on LLLDs testify.

Similar observations apply to all the other elements in the implementation
stack. You can't (as a team) produce software that even pretends to be of
professional quality, unless someone in that team is able to reason at the LLLD
about how it works. That may not the be the same person for each layer in the
stack (often it is, though), nor does that person (or any of those persons)
have to be the team's technical lead. But unless you have someone on the team
who thinks that way, and has that kind of knowledge, then you don't know
/whether/ you have considered all the relevant details. Unless the knowledge
extends beyond the relevant into the (apparently) irrelevant then you can never
be sure you are not standing on quicksand.

Someone has to know where the bedrock is.

-- chris
 
T

Thomas Hawtin

Chris said:
Thomas Hawtin wrote:




Sorry, I'm not sure what you are getting at here ?

From Doug Lea's Concurrent Programming in Java (2nd Ed), 2.1
Immutability: "If an object cannot change state, then it can never
encounter conflicts or inconsistencies when multiple activities attempt
to change its state in incompatible ways."

Before 5.0 you technically had to make sure that each thread that
accessed it had been through the usual synchronise process. So String
can appear to mutate.

From 5.0, if you follow the advice 2.1.2 Construction, then you can
share immutable objects between threads without synchronisation or
volatiles. However, if a thread writes a reference to an immutable,
there is no guarantee that another thread will pick it up.

My weblog has an example of using this behaviour to cache Longs.
Equivalent code can cache Strings and more complex immutables.

http://jroller.com/page/tackline/20041228

Tom Hawtin
 
T

Thomas Hawtin

John said:
If you consider the question at the bytecode level then that's the only
behavior that makes sense: monitorenter and monitorexit bytecodes are
not required to be paired, therefore their behavior must be specified
independent of each other, and the concept of a "synchronized block" is
foreign.

I wasn't sure about this, so I looked it up. It turns out that a JVM
implementation may or may not enforce structured locking.

http://java.sun.com/docs/books/vmspec/2nd-edition/html/Threads.doc.html#22500

However, even if the rules are enforced it doesn't appear that unlocks
have to appear in the reverse order of locks.

Tom Hawtin
 
Ad

Advertisements


Top