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

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.