13

I have a question about code reordering and race conditions in Java.

Assume I have the following code, with 2 or more threads simultaneously executing workForThread():

public class Job {
   private Lock lock = new ReentrantLock();
   private int sharedObject = 1;
   public void workForThread() {
       lock.lock();
       try {
           sharedObject++;
       } finally {
           lock.unlock();
       }
   }
}

Is it possible that the JVM could execute this in the wrong order? For example, is the following reordering possible?:

sharedObject++;
lock.lock();
lock.unlock();

Or is it guaranteed that the lock will not be reordered?

Chetan Kinger
  • 15,069
  • 6
  • 45
  • 82
Ryu
  • 141
  • 5
  • Generally speaking, the JVM can reorder instructions between memory barrier instructions if it finds out that reordering will lead to "eventual consistency" and improve performance. If reordering doesn't lead to eventual consistency, then JVM won't reorder instructions. – TheLostMind Feb 17 '17 at 06:33
  • Related: http://stackoverflow.com/questions/2576443/java-memory-model-reordering-and-concurrent-locks?rq=1 – Thilo Feb 17 '17 at 06:41
  • 1
    @Ryu You can't create an instance of `Lock` as it is an interface. I believe you meant to instantiate `ReentrantLock` or some other implementation of the lock interface `Lock`? If not, can you show us what your `Lock` implementation looks like? – Chetan Kinger Feb 17 '17 at 06:52
  • @CKing Sorry for that, it can be seen as a implementation of official `Lock` interface. – Ryu Feb 17 '17 at 07:12
  • 1
    The `lock.lock()` call should come _before_ the `try` block. The `Lock` interface does not prohibit `lock.lock()` from throwing an exception, and you would not want your code to call `lock.unlock()` if that happened. – Solomon Slow Feb 17 '17 at 19:52
  • 1
    `Lock` would be *fantastically useless* if it allowed this sort of reordering. – Boann Feb 18 '17 at 17:46

1 Answers1

9

Let's take a look at what the Java Docs says about the Lock interface :

All Lock implementations must enforce the same memory synchronization semantics as provided by the built-in monitor lock, as described in section 17.4 of The Java™ Language Specification:

A successful lock operation has the same memory synchronization effects as a successful Lock action.

A successful unlock operation has the same memory synchronization effects as a successful Unlock action.

So the answer to your question is yes. Lock gives you the same reordering guarentee that a regular synchronized block/method would.

Chetan Kinger
  • 15,069
  • 6
  • 45
  • 82
  • But you are not `synchronizing` on the `Job` instance, only on the `Lock` instance. Does that still work then? – Thilo Feb 17 '17 at 06:40
  • @Thilo Yes. As long as all threads use the same `Lock`. (I am assuming `Lock` is the built in interface provided by the JDK) – Chetan Kinger Feb 17 '17 at 06:42
  • I suppose you are right. The JMM says that thread B can see *everything* done by thread A if B obtained a lock that A released. (happens-before relationsship). If that didn't work the Good Practice of having these dedicated `private final Object lock = new Object()` to synchronize on wouldn't work either. – Thilo Feb 17 '17 at 06:43
  • @Thilo True. I would only assume that the memory effects of `synchronized` and `Lock` would be inline. – Chetan Kinger Feb 17 '17 at 06:51
  • Sorry, do you guys mean that the multi-thread actions to the same object (like `lock`) will be considered as a happened-before relation? – Ryu Feb 17 '17 at 06:57
  • @Ryu Yes. Any operations done between accessing and releasing the `Lock` will be considered as *happens-before*. That said, we are assuming that `Lock` is an implementation of `java.util.concurrent.locks.Lock`. (`new Lock()` wouldn't work since `Lock` is an interface) – Chetan Kinger Feb 17 '17 at 07:01
  • @CKing But the relation between `lock` and `shareObj` is not explicit. Does JMM still know that the `lock.lock()` can not be executed after `shareObj ++`? – Ryu Feb 17 '17 at 07:10
  • @Ryu Think of `lock.lock` and `lock.unlock` as a `sycnrhonized(lock)` { } block. Anything that happens within a `synchronized` block will be visible to other threads that `synchnronize` on the same lock (`shareObj++` in your case). See [this](http://stackoverflow.com/questions/42107083/memory-effects-of-synchronized-keyword-in-java/42107394#42107394) and [this](http://stackoverflow.com/questions/42163468/do-we-need-to-synchronize-writes-if-we-are-synchronizing-reads/42164611#42164611). `lock.lock` and `lock.unlock` has the same guarantees as mentioned in these answers with `synchronized` – Chetan Kinger Feb 17 '17 at 07:16
  • @CKing If we don't implement the `java.util.concurrent.locks.Lock` interface but rebuild a wheel which just inherit `Object` called `PureLock`. Does Java Compiler still keep the execution order to make sure the memory coherence? I means there are no any black magic behind the `java.util.concurrent.locks.Lock`, right? – Ryu Feb 17 '17 at 07:17
  • 1
    @Ryu Firstly, If you create your own object called `PureLock`, it won't have the methods named `lock` and `unlock`. The only option you will have is to use a `synchronized(pureLock)` block to use it as a lock. This is known as an intrinsic lock; however, the `lock` and `unlock` methods in `Lock` are doing what `synchronized(pureLock)` would do for you. I don't know what you mean by *black magic* but I think you get the point? `Lock` is not a POJO and does some work inside `lock` and `unlock`. `Lock` allows you to do additional things that you can't do with a POJO such as `tryLock`. Clear? – Chetan Kinger Feb 17 '17 at 07:22
  • @CKing Yes Thanks a lot! :-) p.s. Black magic means that Java compiler will check a object's class whether a `java.util.concurrent.locks.Lock` to build the relation to make sure the execution order. – Ryu Feb 17 '17 at 07:26
  • 1
    There is no "black magic" involved but the `Lock` implementations provided by the JDK do make use of lower-level primitives that you cannot just emulate in "pure" code. And I don't think that reordering of instructions happens during Java compilation (ahead-of-time), but is all done by the JDK (at run-time). – Thilo Feb 17 '17 at 10:36
  • What about `java.util.concurrent.Semaphore` or other `java.util.concurrent` lock ? – shaoyihe Oct 31 '18 at 03:00