简体   繁体   中英

Java volatile reordering prevention scope

Writes and reads to a volatile field prevent reordering of reads/writes before and after the volatile field respectively. Variable reads/writes before a write to a volatile variable can not be reordered to happen after it, and reads/writes after a read from a volatile variable can not be reordered to happen before it. But what is the scope of this prohibition? As I understand volatile variable prevents reordering only inside the block where it is used, am I right?

Let me give a concrete example for clarity. Let's say we have such code:

int i,j,k;
volatile int l;
boolean flag = true;

void someMethod() {
    int i = 1;
    if (flag) {
        j = 2;
    }
    if (flag) {
        k = 3;
        l = 4;
    }
}

Obviously, write to l will prevent write to k from reordering, but will it prevent reordering of writes to i and j in respect to l ? In other words can writes to i and j happen after write to l ?

UPDATE 1

Thanks guys for taking your time and answering my question - I appreciate this. The problem is you're answering the wrong question. My question is about scope, not about the basic concept. The question is basically how far in code does complier guarantee the "happens before" relation to the volatile field. Obviously compiler can guarantee that inside the same code block, but what about enclosing blocks and peer blocks - that's what my question is about. @Stephen C said, that volatile guarantees happen before behavior inside the whole method's body, even in the enclosing block, but I can not find any confirmation to that. Is he right, is there a confirmation somewhere?

Let me give yet another concrete example about scoping to clarify things:

setVolatile() {
   l = 5;
} 

callTheSet() {
   i = 6;
   setVolatile();
}

Will compiler prohibit reordering of i write in this case? Or maybe compiler can not/is not programmed to track what happens in other methods in case of volatile, and i write can be reordered to happen before setVolatile() ? Or maybe compiler doesn't reorder method calls at all?

I mean there is got to be a point somewhere, when compiler will not be able to track if some code should happen before some volatile field write. Otherwise one volatile field write/read might affect ordering of half of a program, if not more. This is a rare case, but it is possible.

Moreover, look at this quote

Under the new memory model, it is still true that volatile variables cannot be reordered with each other. The difference is that it is now no longer so easy to reorder normal field accesses around them.

"Around them". This phrase implies, that there is a scope where volatile field can prevent reordering.

Obviously, write to l will prevent write to k from reordering, but will it prevent reordering of writes to i and j?

It is not entirely clear what you mean by reordering; see my comments above.

However, in the Java 5+ memory model, we can say that the writes to i and j that happened before the write to l will be visible to another thread after it has read l ... provided that nothing writes i and j after write to l .

This does have the effect of constraining any reordering of the instructions that write to i and j . Specifically, they can't be moved to after the memory write barrier following the write to l , because that could lead them to not being visible to the second thread.

But what is the scope of this prohibition?

There isn't a prohibition per se .

You need to understand that instructions, reordering and memory barriers are just details of a specific way of implementing the Java memory model. The model is actually defined in terms of what is guaranteed to be visible in any "well-formed execution".

As I understand volatile prevents reordering inside the block where it is used, am I right?

Actually, no. The blocks don't come into the consideration. What matters is the (program source code) order of the statements within the method.


@Stephen C said, that volatile guarantees happen before behavior inside the whole method's body, even in the enclosing block, but I can not find any confirmation to that.

The confirmation is JLS 17.4.3 . It states the following:

Among all the inter-thread actions performed by each thread t, the program order of t is a total order that reflects the order in which these actions would be performed according to the intra-thread semantics of t.

A set of actions is sequentially consistent if all actions occur in a total order (the execution order) that is consistent with program order, and furthermore, each read r of a variable v sees the value written by the write w to v such that:

  • w comes before r in the execution order, and

  • there is no other write w' such that w comes before w' and w' comes before r in the execution order.

Sequential consistency is a very strong guarantee that is made about visibility and ordering in an execution of a program. Within a sequentially consistent execution, there is a total order over all individual actions (such as reads and writes) which is consistent with the order of the program, and each individual action is atomic and is immediately visible to every thread.

If a program has no data races, then all executions of the program will appear to be sequentially consistent.

Notice that there is NO mention of blocks or scopes in this definition.

EDIT 2

The volatile ONLY gaurentee the happens-before relation .

Why it reorder in single thread

Considered we have two fields:

int i = 0;
int j = 0;

We have a method to write them

void write() {
  i = 1;
  j = 2;
}

As you know, compiler may reorder them. That is because compiler think it is not matter access which first. Because in single thread, they are 'happen together'.

Why can't reorder in multi thread

But now we have another method to read them in another thread:

void read() {
  if(j==2) {
    assert i==1;
  }
}

If compiler still reorder it, this assert may fail. That means j has been 2 , but i unexpectly is not 1 . Which seems i=1 is happens after assert i==1 .

What volatile do

The volatile only gaurantee the happens-before relation .

Now we add volatile

volatile int j = 0;

When we observe j==2 is true, that means j=2 is happened and i=2 is before it, it must happened. So the assert will never fail now.

'Prventing reorder' is just an approach that compiler to provide that guarantee.

Conclusion

The only things you should now is happens-before . Please refer to the link below of java specification. The reordering or not is just a side effect of this guarantee.

Answer for you question

Since l is volatile , acccess to i and j always before access to l in the someMethod . The fact is, every thing before the line l=4 will happen before before it.

EDIT 1

Since the post has been edit. Here is further explasion.

A write to a volatile field (§8.3.1.4) happens-before every subsequent read of that field.

happens-before means:

If one action happens-before another, then the first is visible to and ordered before the second.

So the access to i and j happen-before access to l .

reference: https://docs.oracle.com/javase/specs/jls/se10/html/jls-17.html#jls-17.4.5

Origin answer

No, the volatile only protect itself, though it is not easy to reorder field access near volatile .

Under the new memory model, it is still true that volatile variables cannot be reordered with each other. The difference is that it is now no longer so easy to reorder normal field accesses around them. Writing to a volatile field has the same memory effect as a monitor release, and reading from a volatile field has the same memory effect as a monitor acquire. In effect, because the new memory model places stricter constraints on reordering of volatile field accesses with other field accesses, volatile or not, anything that was visible to thread A when it writes to volatile field f becomes visible to thread B when it reads f.

The volatile keyword only guarantee that:

A write to a volatile field happens before every subsequent read of that same volatile.

reference: http://www.cs.umd.edu/~pugh/java/memoryModel/jsr-133-faq.html#volatile

I am curious to know how volatile variable affects OTHER fields

Volatile variables do affect the other fields. JIT compiler can reorder the instructions if he thinks that reordering will not have any impact on the execution output. So if you have 6 independent variable stores JIT can reorder the instructions.

However if you make a variable volatile ie in your case variable l then JIT will not reorder any variable STORES after the volatile STORE. And I think that makes sense because in a multithreaded program if I get the value of variable l as 4, then I should get i as 1, because in my program i was written before l and which eventually is Program Order Semantics (If I am not wrong).

Note that volatile variables does two things:
  1. Compiler will not reorder any stores after volatile store / not reorder any reads before volatile read.
  2. Flushes the Load/Store buffer so that all the processor can see the changes.

EDIT:

Good blog here: http://jpbempel.blogspot.com/2013/05/volatile-and-memory-barriers.html

Maybe I know the "real scope" you are in dout.

Two types of reorder is the main reason of unordering instruction result: 1. Compiler optimization 2. Cpu processor recordering(maily caused by cache and main memory synchronize)

volatile keyword first need to confirm the flushing of volatile variable, at the meantime, other variables are also flushed to main memory.But because of compiler reordering, some writable instructions before the volatile valatile variable may be reordered after the volatile variable, the reader may be confused to read not the real time other variable values which is before the volatile variable in program order, so the rule of "variables writting instruction before the volatile variable is forced to run before the volatile" is made.This optimazation is done by Java Compiler or JIT.

The main point is optimization of compiler in instructions,like finding dead code , instruction reorder operation, the instructions code range is always a "basic block"(Except some other constant propagation optimization, etc.). A basic block is an set of instructions without jmp instruction inside , so this is a basic block. So in my opinion, the reorder operation is fixed in the range basic block. the basic block in source code is always a block or the body of a method.

And also because java does not have inline function, the method call is used by dynamic invoke method instruction, the reorder operation should not be across two method.

So, the scope will not be larger than a "method body", or maybe only a area of "for" body , it's the basic block range.

This is all my thought, I'm not sure if it is right, someone can help to make it more accurate.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM