While reading the source code of java.util.concurrent.locks.ReentrantLock
I found that the tryLock()
method is implemented as below:
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;
}
We try to "own" the lock or check if we have already owned the lock, depending on the state
maintained in AbstractQueuedSynchronizer
. But I wonder why the variable state
is declared as volatile but the variable exclusiveOwnerThread
is not?
To understand why exclusiveOwnerThread
does not need to be volatile it helps to look at both the acquire and release methods together.
Acquire method1:
/**
* Performs non-fair tryLock. tryAcquire is implemented in
* subclasses, but both need nonfair try for trylock method.
*/
@ReservedStackAccess
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;
}
Release method:
@ReservedStackAccess
protected final boolean tryRelease(int releases) {
int c = getState() - releases;
if (Thread.currentThread() != getExclusiveOwnerThread())
throw new IllegalMonitorStateException();
boolean free = false;
if (c == 0) {
free = true;
setExclusiveOwnerThread(null);
}
setState(c);
return free;
}
It's also important to realize that exclusiveOwnerThread
does not reference some arbitrary object with no relation to the threads involved. It specifically holds a reference to a Thread
instance and is compared strictly to the calling thread. In other words, what matters is if:
Thread.currentThread() == getExclusiveOwnerThread()
Which will be true if, and only if, the calling thread has previously invoked #setExclusiveOwnerThread(Thread)
, with itself as the argument, due to the combined natures of #nonfairTryAcquire(int)
and #tryRelease(int)
. Actions in one thread always happen-before subsequent actions in the same thread.
So if c != 0
then there are two scenarios:
The calling thread owns the synchronizer.
getExclusiveOwnerThread()
will return a reference to the calling thread.The calling thread does not own the synchronizer.
It no longer matters what reference is returned by getExclusiveOwnerThread()
because it is impossible for that method to return a reference to the calling thread.
The calling thread can never see a stale reference to itself because of the setExclusiveOwnerThread(null)
call in #tryRelease(int)
. This means getExclusiveOwnerThread()
can either return null
or some other Thread
reference (stale or not), but never a reference to the calling thread.
The reason state
must be volatile is because it is shared between threads in a manner where it's paramount each thread sees the most recent value.
1. The implementation of FairSync#tryAcquire(int)
has nearly the same implementation except it takes the order of calling threads into account.