Does synchronized keyword prevent reordering in Java?
Asked Answered
W

3

8

Suppose I have the following code in Java

a = 5;
synchronized(lock){
    b = 5;
}
c = 5;

Does synchronized prevent reordering? There is no dependency between a, b and c. Would assignment to a first happen then to b and then to c? If I did not have synchronized, the statements can be reordered in any way the JVM chooses right?

Walden answered 24/4, 2014 at 16:0 Comment(4)
Anything that happens is solely dependent on the JVM and performance will not be affected. Why do you want to find out whether reordering has been done or not?. Synchronization is at object level.Swats
In the actual code, in place of (c=5), I am returning a value that another thread needs to start and if the third statement gets executed before the second, the code will fail. I wanted there to be no reordering between a couple of statements and wanted to know if synchronized would prevent the reorderingWalden
If you need c = 5 to be visible to a running thread, you must place it inside the synchronized block. Otherwise there is no guarantee the other thread will ever see this value.Lollop
And even placing c=5 into the synchronized is not enough. The thread reading the variable c must do so within a synchronized block synchronizing on the same object to ensure a correct read. But even that only guarantees that the reading thread will read the updated value when it executes the synchronized block containing the read after the writing thread executed the synchronized block containing the write. synchronized only guarantees that there is no “at the same time” and hence enforces a notable order. But this alone still doesn’t say which order it will be.Chole
L
5

Does synchronized prevent reordering?

It prevents some re-ordering. You can still have re-ordering outside the synchronized block and inside the synchronized block, but not from inside a synchronized block, to outside it.

There is no dependency between a, b and c.

That makes no difference.

Would assignment to a first happen then to b and then to c?

Yes. But as has been noted, this is not guaranteed for all JVMs. (See below)

If I did not have synchronized, the statements can be reordered in any way the JVM chooses right?

Yes, by the JVM and/or the CPU instruction optimiser and/or CPU cache, but it is unlikely given there is no obvious reason to suspect that changing the order of a = 5; and b = 5; will improve performance.

What you could see is a change of visibility for the cache. i.e. another thread reading these values could see the b = 5; before a = 5; e.g. they are on different cache lines, if it is not also synchronized.

Lollop answered 24/4, 2014 at 16:9 Comment(11)
To the best of my understanding this is wrong. We can pull as much extra writes/reads into a synchronized block and reorder according to normal rules inside as much as we want. We just have to make sure that the hb relationship between subsequent locks and unlocks of the monitor isn't violated - which it isn't even if we reorder all three writes (as long as it's guaranteed that after the unlock the next thread who locks sees a = 5; b=5)Zena
@Zena That is pretty much what I said. In the case of writes only, the read hb don't apply. As the OP states, the statements are just examples.Lollop
"Would assignment to a first happen then to b and then to c?" - "yes". For me that reads as a acknowledgement of a happens-before relationship between the three writes which doesn't exist. Especially since the next question you answer is what would change if no synchronized were provided (answer wrt reordering: Nothing at all!). Maybe just mention that synchronized(lock) { c=5; b = 5; a=5; } is perfectly valid according to the spec, to make it clear to everyone?Zena
@Zena So are you suggesting that you might see c = 5 before b = 5Lollop
Indeed that's my reading of the JMM here. Instead of quoting the JMM I'll refer to this by Doug Lea and the first table which states the reorderings. You'll see that MonitorExit, NormalStore can be reordered. Or if we go by the JLS: The hb relationship between unlocking the monitor and locking it only guarantees that we'll see a=5 if b=5 happened, but it doesn't say what value we see for c.Zena
@Zena You have a point that the spec doesn't prohibit it, only that AFAIK this doesn't happen. The memory barrier on Intel processors doesn't allow for such a reordering, but on an ARM processor or something else, perhaps it might.Lollop
It's a valid compiler optimization to change the write order here for whatever reason so this may just as well happen on x86 as well. Not a processor optimization that x86 would do I agree, but I can say that Aarch64 would certainly be well in its rights to do so. No idea about previous ARM ISAs, haven't had to study those in detail.Zena
@Zena AFAIK the JVM issues an ordered write on exiting the block to ensure it is visible to other threads. The CPU won't re-order around such an ordered write.Lollop
One possible implementation, but even then on Aarch64 the CPU could still reorder the later writes to complete first and in any case the compiler is perfectly in its right to change the given code into synchronized(lock) { c=5; b = 5; a=5; } before it even emits any machine code. I'm not arguing that under one particular implementation under one particular ISA this won't work, I'm saying that it is misleading in the general case. Double checked logging without volatile will also work most of the time, but you still wouldn't recommend doing so, right?Zena
It's unlikely, that the operations will be reordered in this simplified example. However, if there is another write to c before b and within the synchronized block, most compilers will move the second write to c into the synchronized block and merge them into one write. The same can be done for a. So I agree with Voo: In general, it's wrong to assume, that a synchronized block will enforce this ordering. See also my answer.Leprosarium
@Zena I have added a clarification and suggestion to read your comments.Lollop
R
6

Locking the assignment to b will, at the very least, introduce an acquire-fence before the assignment, and a release-fence after the assignment.

This prevents instructions after the acquire-fence to be moved above the fence, and instructions before the release-fence to be moved below the fence.

Using the ↓↑ notation:

a = 5;
↓ 
b = 5;
↑
c = 5;

The ↓ prevents instructions from being moved above it. The ↑ prevents instructions from being moved below it.

Rupert answered 24/4, 2014 at 16:10 Comment(6)
@WhoAmI Some reordering may happen. To put it simply, some instructions may be moved inside the synchronized block, but no instruction can ever be moved outside the synchronized block.Rupert
@WhoAmI I've expanded my answer, hope it's clearer now.Rupert
What is achieved by moving the code inside the blocks?Swats
@WhoAmI The compiler, the runtime or the processor may decide to do so to improve performance. If you're asking "how does the processor know moving an instruction inside the block will improve performance?", then I have no idea.Rupert
One example of optimization is to join two or more adjacent synchronized blocks into one. This is also possible if there are some instructions between these blocks. But to stay at the example of this question, suppose a, b, and c are on contiguous memory locations and the hardware is capable of transferring multiple ints in one operation (that’s not too far-fetched). Then it would be an improvement to do a lock-transferall-unlock rather than transferone-lock-transferone-unlock-transferone…Chole
@Rupert - Any book where I can laern more about synchronization? :)Swats
L
5

Does synchronized prevent reordering?

It prevents some re-ordering. You can still have re-ordering outside the synchronized block and inside the synchronized block, but not from inside a synchronized block, to outside it.

There is no dependency between a, b and c.

That makes no difference.

Would assignment to a first happen then to b and then to c?

Yes. But as has been noted, this is not guaranteed for all JVMs. (See below)

If I did not have synchronized, the statements can be reordered in any way the JVM chooses right?

Yes, by the JVM and/or the CPU instruction optimiser and/or CPU cache, but it is unlikely given there is no obvious reason to suspect that changing the order of a = 5; and b = 5; will improve performance.

What you could see is a change of visibility for the cache. i.e. another thread reading these values could see the b = 5; before a = 5; e.g. they are on different cache lines, if it is not also synchronized.

Lollop answered 24/4, 2014 at 16:9 Comment(11)
To the best of my understanding this is wrong. We can pull as much extra writes/reads into a synchronized block and reorder according to normal rules inside as much as we want. We just have to make sure that the hb relationship between subsequent locks and unlocks of the monitor isn't violated - which it isn't even if we reorder all three writes (as long as it's guaranteed that after the unlock the next thread who locks sees a = 5; b=5)Zena
@Zena That is pretty much what I said. In the case of writes only, the read hb don't apply. As the OP states, the statements are just examples.Lollop
"Would assignment to a first happen then to b and then to c?" - "yes". For me that reads as a acknowledgement of a happens-before relationship between the three writes which doesn't exist. Especially since the next question you answer is what would change if no synchronized were provided (answer wrt reordering: Nothing at all!). Maybe just mention that synchronized(lock) { c=5; b = 5; a=5; } is perfectly valid according to the spec, to make it clear to everyone?Zena
@Zena So are you suggesting that you might see c = 5 before b = 5Lollop
Indeed that's my reading of the JMM here. Instead of quoting the JMM I'll refer to this by Doug Lea and the first table which states the reorderings. You'll see that MonitorExit, NormalStore can be reordered. Or if we go by the JLS: The hb relationship between unlocking the monitor and locking it only guarantees that we'll see a=5 if b=5 happened, but it doesn't say what value we see for c.Zena
@Zena You have a point that the spec doesn't prohibit it, only that AFAIK this doesn't happen. The memory barrier on Intel processors doesn't allow for such a reordering, but on an ARM processor or something else, perhaps it might.Lollop
It's a valid compiler optimization to change the write order here for whatever reason so this may just as well happen on x86 as well. Not a processor optimization that x86 would do I agree, but I can say that Aarch64 would certainly be well in its rights to do so. No idea about previous ARM ISAs, haven't had to study those in detail.Zena
@Zena AFAIK the JVM issues an ordered write on exiting the block to ensure it is visible to other threads. The CPU won't re-order around such an ordered write.Lollop
One possible implementation, but even then on Aarch64 the CPU could still reorder the later writes to complete first and in any case the compiler is perfectly in its right to change the given code into synchronized(lock) { c=5; b = 5; a=5; } before it even emits any machine code. I'm not arguing that under one particular implementation under one particular ISA this won't work, I'm saying that it is misleading in the general case. Double checked logging without volatile will also work most of the time, but you still wouldn't recommend doing so, right?Zena
It's unlikely, that the operations will be reordered in this simplified example. However, if there is another write to c before b and within the synchronized block, most compilers will move the second write to c into the synchronized block and merge them into one write. The same can be done for a. So I agree with Voo: In general, it's wrong to assume, that a synchronized block will enforce this ordering. See also my answer.Leprosarium
@Zena I have added a clarification and suggestion to read your comments.Lollop
L
4

Does synchronized prevent reordering?

Partially, see below.

Would assignment to a first happen then to b and then to c?

No. As dcastro pointed out, actions can be moved into synchronized blocks. So the compiler would be allowed to generate code, that corresponds to the following statements:

synchronized (lock){
    a = 5;
    b = 5;
    c = 5;
}

And the compiler is also allowed to reorder statements within a synchronized block, that have no dependency to each other. So the compiler can also generate code that corresponds to the following statements:

synchronized (lock){
    c = 5;
    b = 5;
    a = 5;
}

If I did not have synchronized, the statements can be reordered in any way the JVM chooses right?

Well, I think that's the wrong question, and it's also the wrong way to think about the Java Memory Model. The Java Memory Model is not defined in terms of reorderings. Actually, it's much easier than most people think. Basically, there is only one important rule, that you can find in §17.4.5 in the Java Language Specification:

A program is correctly synchronized if and only if all sequentially consistent executions are free of data races. If a program is correctly synchronized, then all executions of the program will appear to be sequentially consistent.

In other words: If you completely ignore reorderings, and for all possible executions of the program, there is no way that two threads access the same memory location, which is neither volatile nor atomic, and at least one of the actions is a write operation, then all executions will appear, as if there are no reorderings.

In short: Avoid data races, and you will never observe a reordering.

Leprosarium answered 24/4, 2014 at 16:59 Comment(1)
+1, it’s worth mentioning that the correct synchronization applies to threads synchronizing on the same lock instance and hence, the JVM can elide the synchronized entirely once it has proven that there is no other thread having access the the specific lock instance.Chole

© 2022 - 2024 — McMap. All rights reserved.