简体   繁体   中英

Java - how does Lock guarantee happens-before relationship?

Let's consider the following standard synchronization in Java:

public class Job {
   private Lock lock = new ReentrantLock();

   public void work() {
       lock.lock();
       try {
           doLotsOfWork();
       } finally {
           lock.unlock();
       }
   }
}

I understand, based on Javadoc, that this is equivalent to synchronized block. I am struggling to see how this is actually enforced on the lower-level.

Lock has a state which is a volatile, upon call to lock() it does a volatile read, then upon release it performs a volatile write. How can a write to a state of one object ensure, that none of the instruction of doLotsOfWork , which might touch lots of different objects, will not be executed out of order?

Or imagine that doLotsOfWork is actually substituted with 1000+ lines of code. Clearly the compiler cannot know in advance that there is a volatile somewhere inside the lock, therefore it needs to stop re-ordering the instructions. So, how is happens-before guaranteed for lock/unlock , even though it is built around volatile state of a separate object?

Well, if I understood correctly then your answer is here . volatile writes and reads introduce memory barriers : LoadLoad , LoadStore , etc. that forbid re-orderings. At the CPU level this is translated to actual memory barriers like mfence or lfence (the CPU forces the non-reordering via some other mechanisms too, so you might see something else in the machine code as-well).

Here is a small example:

i = 42;
j = 53;
[StoreStore]
[LoadStore]
x = 1; // volatile store

i and j assignments can be re-ordered between then, but they can not with x=1 or in other words i and j can not go below x.

Same applies to the volatile reads .

For your example every operation inside doLotsOfWork can be re-ordered as the compiler pleases, but it can not be re-ordered with lock operations .

Also when you say that the compiler can not know that there is a volatile read/write , you are slightly wrong. It has to know that , otherwise there would be no other way to prevent those re-orderings.

Also, last note: since jdk-8 you can enforce non re-orderings via the Unsafe that provides ways to that besides volatile.

From Oracle's documentation :

A write to a volatile field happens-before every subsequent read of that same field. Writes and reads of volatile fields have similar memory consistency effects as entering and exiting monitors, but do not entail mutual exclusion locking.

Java Concurrency in Practice states it even more clearly:

The visibility effects of volatile variables extend beyond the value of the volatile variable itself. When a thread A writes to a volatile variable and subsequently thread B reads that same variable, the values of all variables that were visible to A prior to writing to the volatile variable become visible to B after reading the volatile variable.

Applied to ReentrantLock it means that everything executed before lock.unlock() ( doLotsOfWork() in your case) will be guaranteed to happen before subsequent call to lock.lock() . Instructions inside doLotsOfWork() still can be reordered among themselves. The only thing that is guaranteed here is that any thread which will subsequently acquire the lock calling lock.lock() will see all changes done in doLotsOfWork() before calling lock.unlock() .

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