Volatile happens before question

R

raphfrk

The spec says that all writes to volatiles can be considered to happen
before all subsequent reads. What does "subsequent" mean, is that
with regards to real time?

So,

Thread 1
int b = 0;
volatile boolean a = false;
....
....
b = 1;
a = true;

Thread 2
if (a) {
System.out.println("The value of b is " + b);
}

Since the setting of a to true happens before the reading of a as
true, the println must happen after b is set to 1.

This means that either nothing will be printed or "The value of b is
1" will be printed.

Does this work in reverse too?

For example,

Thread 1
int b = 0;
volatile boolean a = false;
....
....
a = true;
b = 1;

Thread 2
int bStore = b;
if (!a) {
System.out.println("The value of b is " + bStore);
}

Will this always print either "The value of b is 0" or nothing.

(bStore = b) happens before (read a as false)
(read a as false) happens before (set a = true) [is this valid?]
(set a = true) happens before (set b = 1)

So, bStore = b happens before set b = 1, so bStore = 0.

Effectively, the rule would be "A read to a volatile happens before
the write to that volatile that overwrites the value that was read".

However, that wasn't clear from the spec. I think since read/writes
to volatiles are synchronization actions, then when running the
program, they can be considered to have happened in some ordering
(consistent with program order in the threads). As long as the
program works no matter what the ordering is picked, then it is fine.
 
K

Knute Johnson

The spec says that all writes to volatiles can be considered to happen
before all subsequent reads. What does "subsequent" mean, is that
with regards to real time?

So,

Thread 1
int b = 0;
volatile boolean a = false;
...
...
b = 1;
a = true;

Thread 2
if (a) {
System.out.println("The value of b is " + b);
}

Since the setting of a to true happens before the reading of a as
true, the println must happen after b is set to 1.
Yes

This means that either nothing will be printed or "The value of b is
1" will be printed.
Yes

Does this work in reverse too?

For example,

Thread 1
int b = 0;
volatile boolean a = false;
...
...
a = true;
b = 1;

Thread 2
int bStore = b;
if (!a) {
System.out.println("The value of b is " + bStore);
}

Will this always print either "The value of b is 0" or nothing.
Yes

(bStore = b) happens before (read a as false)
(read a as false) happens before (set a = true) [is this valid?]
(set a = true) happens before (set b = 1)

So, bStore = b happens before set b = 1, so bStore = 0.

Only if a is false
Effectively, the rule would be "A read to a volatile happens before
the write to that volatile that overwrites the value that was read".

I don't think so but I'm not really sure what you mean.
However, that wasn't clear from the spec. I think since read/writes
to volatiles are synchronization actions, then when running the
program, they can be considered to have happened in some ordering
(consistent with program order in the threads). As long as the
program works no matter what the ordering is picked, then it is fine.

Not sure what you really mean here. There is also a side effect of
writing a volatile variable and that is that all other variable writes
that happened before in that thread are visible to any other thread that
subsequently reads the volatile variable. "Locking can guarantee both
visibility and atomicity; volatile variables can only guarantee
visibility" Java Concurrency in Practice, Brian Goetz. You should buy a
copy of that book if you are going to get serious about concurrent
programming.
 
M

markspace



I'm pretty sure Peter got the correct answer which is "no." I want to
stick my answers in a separate reply, so I'll just add that this is an
easy mistake to make and if both Peter and you hadn't already provided
some insights I probably would have flubbed it up myself.

Reasoning about synchronization can be pretty hard. Make sure you
consider carefully all possibilities when analyzing multi-threaded
programs for bugs.
 
K

Knute Johnson

I'm pretty sure Peter got the correct answer which is "no." I want to
stick my answers in a separate reply, so I'll just add that this is an
easy mistake to make and if both Peter and you hadn't already provided
some insights I probably would have flubbed it up myself.

Reasoning about synchronization can be pretty hard. Make sure you
consider carefully all possibilities when analyzing multi-threaded
programs for bugs.

OK, I'll bite, if a is false how can b be anything but 0?
 
M

markspace

It's this last assertion that fails.


I believe the original assertion is true, but in code he was assuming
the converse. (*) That is a is true before b is 1, but just because a
is true doesn't mean that b has been set to 1 yet. There is a physical
time gap in between those events. (**)

In other words, the JLS does not say that:

a is true iff b is 1.

(iff = "if and only if").

The JLS does say:

If b is 1, then a has also been set to true. (a happens before b.)

The JLS does not say:

If a is true, then b has been also set to 1.

That last statement could be false and there could still be a valid
happens-before relationship between write(a<-true) and write(b<-1).
There is a time gap between a being written and b being written where
you could see a==true but b==0.

As a general rule of thumb, you always read the volatile FIRST and write
the volatile LAST. Here in the 2nd example, a was read first and the
volatile (b) was read after, so that's why it didn't work.



(*) I'm wondering if the proper word is reflexive rather than converse.
That is, just because

(b=1) therefore (a=true) is true, does not mean

(a=true) therefore (b=1) is true (it's not).

Any help here?


(**) These are terrible variable names for discussion a problem in
English. "a" looks like the article a and b is just as bad. Use
something longer and more obvious next time. I can scarcely follow my
own writing, sheesh.
 
R

raphfrk

I'm not aware of any such rule or its equivalent appearing in the
specification.

The spec says that there is an ordering of the synchronisation
actions.

So, this means that the JVM must order all synchronization actions in
some ordering that is consistent with the program ordering in each
thread.

However, if it places a sync actions later than another sync action,
that still doesn't mean that the later one "happens after" the first
one?
 
R

raphfrk

I believe the original assertion is true, but in code he was assuming
the converse. (*)  That is a is true before b is 1, but just because a
is true doesn't mean that b has been set to 1 yet.  There is a physical
time gap in between those events. (**)

No, I was assuming that if a = false, then b must be 0.

Effectively, "if a read of a volatile gives the old value, then that
read happens before the write that overwrites that value"

If thread 2 is
a = true;
b = 1;

then if a == false, then b must not yet be set to 1.
 
K

Knute Johnson

OK, I'll bite, if a is false how can b be anything but 0?


And if b = 1 could be reordered before a = true then b would be flopping
all over the place because as soon as a = true b would again have to be 0.
 
M

markspace

And if b = 1 could be reordered before a = true then b would be flopping
all over the place because as soon as a = true b would again have to be 0.


But those reads and writes could be reordered, couldn't they? The read
of "a" could happen after the read of "b", because there's no
happens-before relationship there (a is not volatile, nor synchronized
in any way).

Or is that what you are saying? The actual problem is worse. (And I
did in fact fubar my explanation and the my reading of the code. It
*is* broken though.) I'm still looking for references, and I might be
close. Uno momento....
 
D

Daniel Pitts

The spec says that all writes to volatiles can be considered to happen
before all subsequent reads. What does "subsequent" mean, is that
with regards to real time?

So,

Thread 1
int b = 0;
volatile boolean a = false;
...
...
b = 1;
a = true;

Thread 2
if (a) {
System.out.println("The value of b is " + b);
}

Since the setting of a to true happens before the reading of a as
true, the println must happen after b is set to 1.

This means that either nothing will be printed or "The value of b is
1" will be printed.
This works as you expect, yes...
Does this work in reverse too? Not exactly.

For example,

Thread 1
int b = 0;
volatile boolean a = false;
...
...
a = true;
b = 1;

Thread 2
int bStore = b;
if (!a) {
System.out.println("The value of b is " + bStore);
}

Will this always print either "The value of b is 0" or nothing.

(bStore = b) happens before (read a as false)
(read a as false) happens before (set a = true) [is this valid?]
(set a = true) happens before (set b = 1)
I see no flaw in your reasoning here, though it isn't the "reverse" of
the previous case, it is exactly the same.
So, bStore = b happens before set b = 1, so bStore = 0.

Effectively, the rule would be "A read to a volatile happens before
the write to that volatile that overwrites the value that was read". Yes. that is true.

However, that wasn't clear from the spec. I think since read/writes
to volatiles are synchronization actions, then when running the
program, they can be considered to have happened in some ordering
(consistent with program order in the threads). As long as the
program works no matter what the ordering is picked, then it is fine.
Yes, that is part of the point.

You may wish to invest in a copy of the book "Java Concurrency in
Practice". It is very thorough, explicit, and understandable explanation
of program behavior under concurrent situations.
 
D

Daniel Pitts

But those reads and writes could be reordered, couldn't they? The read
of "a" could happen after the read of "b", because there's no
happens-before relationship there (a is not volatile, nor synchronized
in any way).
a was volatile, so it does cause the happens-before relationships with
the reads/writes of b.
 
M

markspace

a was volatile, so it does cause the happens-before relationships with
the reads/writes of b.


I messed up again, but I'm still right. :)

He's reading "b" first. He stores b in bStore, and then checks "a".

Thread 2
int bStore = b; <--- DON'T MISS THIS BIT
if (!a) {
System.out.println("The value of b is " + bStore);
}
 
D

Daniel Pitts

I messed up again, but I'm still right. :)

He's reading "b" first. He stores b in bStore, and then checks "a".
He is reading b first. If b=1, that means a MUST be true. So, The
original assertion that either nothing gets printed, or "The value of b
is 0" gets printed is true.// if a is true bStore is either 0 or 1, but if a is false, b=1 hasn't
happened yet (since it a=true happens-before b=1).
 
M

markspace

However, if it places a sync actions later than another sync action,
that still doesn't mean that the later one "happens after" the first
one?


Specifically, no.

It wasn't clear to me that was the question you where asking. (Sorry
about that!) Your program was a little hard to read. But the answer is
that there is are no semantics for multi-threaded reads and rights if
there is no happens-before relationship.

What you have there is called a data race. Those are completely broken.
The reason is that hardware involved will optimize reads and writes
aggressively. (The same problem would occur even if you wrote this code
in assembly language. This is not a Java problem, it's a system
problem. The JLS actually just tells you how modern CPUs work.)

In this case, by creating a data race and reading b before you create
the happens-before relationship, Thread 2 could see any value for b,
even values that are neither 1 nor 0. It's a random number generator.
(And the more complete story is even worse. I'm still looking for some
sources to back that up though.)
 
M

markspace

He is reading b first.


Right. "b" is not volatile. So we can't say anything at all about the
value of "b". Full stop.

If b=1, that means a MUST be true.

No, if b = 1, it could just be a random value made up by the CPU.


Check Java Concurrency in Practice. "Programs with data races have no
useful defined semantics." That's exactly what Brian Goetz says, and
this is what he's talking about. Reads and writes no longer work if you
obviate synchronization.
 
M

markspace

Maybe some other folks would like to look too. I've found the blog I
was looking for, but this guy is fairly prolific in his writing and I'm
having trouble finding the article about how CPUs can return random
values when proper signalling is not used.

Take a search through this blog anyway, it's a real eye-opener.

<http://bartoszmilewski.wordpress.com/>
 
R

raphfrk

Thanks for the info. I think the conclusion is that it would be
unsafe? I will just use synchronized to ensure safety.
 
M

markspace

Thanks for the info. I think the conclusion is that it would be
unsafe? I will just use synchronized to ensure safety.


Certainly my conclusion is that it is unsafe. "Synchronization" here
includes use of volatiles. As long as you write the volatile last and
read it first, the program is properly synchronized.
 
M

markspace

Certainly my conclusion is that it is unsafe. "Synchronization" here
includes use of volatiles. As long as you write the volatile last and
read it first, the program is properly synchronized.


Here's a video by the same guy, talking to the Seattle Java Users Group.

<
>

Pay special attention to the "out of thin air guarantee."
 
M

markspace

That is one of the few behaviors that is specifically excluded. "Each
read sees a write to the same variable in the execution.",
http://java.sun.com/docs/books/jls/third_edition/html/memory.html#17.4.7


Well, I know Java can't exclude "made up values" because it happens in
hardware. Java has no choice but to execute on hardware.

The section you are referring to is 17.4.7 Well Formed Executions, and
starts thusly: "We only consider well-formed executions." In a well
formed execution, I believe you are right, the program only sees writes
that another thread actually made.

However a well formed execution also requires "4. The execution is
happens-before consistent" and I believe that's what's being violated
here. Once you start reading variables with out proper synchronization,
the concept of well formed executions are no longer valid and all of
those 5 points go out the window.

This is what Brian Goetz means when he says "Programs with data races
have no useful defined semantics." Data races mean your code is no
longer well formed. Nice things like "you don't see made up values"
don't work. Things are just a mess.

Check that Vimeo link I posted, esp. the part about "out of thin air"
values. It's hard to get your head around but important, I think, to
understand what happens when executions are not well-formed. It
provides good incentive to make sure your code really is synchronized in
all cases.
 

Ask a Question

Want to reply to this thread or ask your own question?

You'll need to choose a username for the site, which only take a couple of moments. After that, you can post your question and our members will help you out.

Ask a Question

Members online

Forum statistics

Threads
473,770
Messages
2,569,583
Members
45,073
Latest member
DarinCeden

Latest Threads

Top