Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How does the piggybacking of current thread variable in ReentrantLock.Sync work?

I read about some of the details of implementation of ReentrantLock in "Java Concurrency in Practice", section 14.6.1, and something in the annotation makes me confused:

Because the protected state-manipulation methods have the memory semantics of a volatile read or write and ReentrantLock is careful to read the owner field only after calling getState and write it only before calling setState, ReentrantLock can piggyback on the memory semantics of the synchronization state, and thus avoid further synchronization see Section 16.1.4.

the code which it refers to:

protected boolean tryAcquire(int ignored) {
    final Thread current = Thread.currentThread();
    int c = getState();
    if (c ==0) {
        if (compareAndSetState(0, 1)) {
             owner = current;
             return true;
        }
     } else if (current == owner) {
         setState(c+1);
         return true;
     }
     return false;
}

And I believe this is the simplified code of the nonfairTryAcquire in the ReentrantLock.Sync.

final boolean nonfairTryAcquire(int acquires) {
    final Thread current = Thread.currentThread();
    int c = getState();
    if (c == 0) {
        if (compareAndSetState(0, acquires)) {
            setExclusiveOwnerThread(current);
            return true;
        }
    }
    else if (current == getExclusiveOwnerThread()) {
        int nextc = c + acquires;
        if (nextc < 0) // overflow
            throw new Error("Maximum lock count exceeded");
        setState(nextc);
        return true;
    }
    return false;
}

So, the baffling part is how the setting of owner, which is merely a plain instance variable in AbstractOwnableSynchronizer, becomes visible to else if (current == owner) in other threads. Indeed, the read of owner is after the calling of getState() (and the state is a volatile qualified variable of AQS), but after the setting of owner, there's nothing (can impose synchronization semantics) at all. Data race happens?

Well, in light of the authority of this book and the thoroughly tested code, two possibilities come to my mind:

  1. The full barrier (be it mfence or 'lock'ed instruction) before the setting owner = current does the hidden work. But from I've learned from several famous articles, the full barrier cares more about the writes before it as well as the reads after it. Well, if this possibility holds true, then some sentences in "JCIP" might be inappropriately stated.

  2. I notice that 'geographically' the setState(c+1) really comes after owner = current in the code snippet, although it's in another branch of if-else. If what the comments says is the truth, does it mean that the barrier inserted by setSate(c+1) can impose synchronization semantics on owner = current in another branch?

I'm a novice in this area, and several great blogs help me a lot in understanding what's underlying the JVM(no ordering):

  • http://mechanical-sympathy.blogspot.com/
  • http://preshing.com/
  • http://bartoszmilewski.com
  • http://psy-lob-saw.blogspot.com/

as well as the always magnificent: http://g.oswego.edu/dl/jmm/cookbook.html

After doing my homework and searching the internet, I fail to come to a satisfying conclusion.

Pardon me if this is too wordy or unclear(English is not my mother tongue). Please help me with this, anything related is appreciated.

like image 430
DarKeViLzAc Avatar asked Sep 11 '13 03:09

DarKeViLzAc


2 Answers

You suspect there could be a race between owner = current; (after the CAS) and if (current == owner) (after reading the state and checking if it is >0).

Taking this piece of code in isolation, I think your reasoning is correct. However, you need to consider tryRelease as well:

 123:         protected final boolean tryRelease(int releases) {
 124:             int c = getState() - releases;
 125:             if (Thread.currentThread() != getExclusiveOwnerThread())
 126:                 throw new IllegalMonitorStateException();
 127:             boolean free = false;
 128:             if (c == 0) {
 129:                 free = true;
 130:                 setExclusiveOwnerThread(null);
 131:             }
 132:             setState(c);
 133:             return free;
 134:         }

Here the owner is set to null before the state is set to 0. To initially acquire the lock, the state must be 0, and so the owner is null.

Consequently,

  • If a thread reaches if (current == owner) with c=1,
    • it can be the owning thread, in which case the owner is correct and the state is incremented.
    • it can be another thread, which can see or not the new owner.
      • If it sees it, everything is fine.
      • If not, it will see null, which is fine as well.
  • If a thread reaches if (current == owner) with c>1,
    • it can be the owning thread, in which case the owner is correct and the state is incremented.
    • it can be another thread, but the owner will be correct for sure.

I aggree that the footnote "read the owner field only after calling getState and write it only before calling setState" in JCIP is misleading. It writes the owner before calling setState in tryRelease, but not tryAcquire.

like image 158
ewernli Avatar answered Sep 21 '22 08:09

ewernli


This is explained quite well in this blog post. Bottom line is that when the reading thread reads a volatile field, all fields updated by the writing thread that were modified before the write to the volatile field will be visible to the reading thread too. The lock class organizes the field accesses to ensure that only the state field needs to be volatile, and the owner field is still safely propogated when needed

like image 41
rolfl Avatar answered Sep 24 '22 08:09

rolfl