conclusion
Give the conclusion first. Taken together, ReentrantLock is an exclusive lock, which can be re-locked, and can be divided into fair locks and unfair locks.
How do fair locks and unfair locks achieve each other? In an unfair lock, when a thread calls lock(), it uses CAS to attempt to acquire the lock (whether the lock is currently idle or not), and the acquisition failure will join the queue and wait. In fair locks, when a thread calls lock(), CAS is used only when the current lock is idle and there are no other threads in the waiting queue. If there are other threads in the waiting queue, it will join the queue and acquire them sequentially. Then we can imagine that the performance of unfair locks is better than fair locks, because when a thread calls lock, the lock may just be released, so it can be occupied by the current thread. If a fair lock is used, the next waiting thread needs to be notified in the queue, and it takes a certain amount of time to wake up the thread. It is also possible that when the thread is awakened, the thread in front of the queue has run out of locks and released.
And how does reentry work? You can see that syn inherited AQS, and AQS inherited AbstractOwnable Synchronizer. In AbstractOwnable Synchronizer, there is a field for recording the thread that last acquired the lock when it was exclusively acquired.
public abstract class AbstractOwnableSynchronizer
implements java.io.Serializable {
/**
* The current owner of exclusive mode synchronization.
*/
private transient Thread exclusiveOwnerThread;
Then ReentrantLock only needs to determine whether the thread calling lock() is equal to the thread recorded in it. If it is equal, it indicates that the thread wants to acquire the lock again. ReentrantLock uses its state+1(state is actually maintained through AQS) to record the number of reentries. At release(), state-1. When state=0, it means that when the lock is released completely, other threads can get the lock.
It is also important to note that the tryLock() methods for unfair locks and fair locks are both used when the locks are available, and CAS is used to try to obtain them, ignoring the waiting queue. But for the tryLock(long timeout, TimeUnit unit) method, the respective tryAcquire() is called. So in summary, there are unfair ways to acquire locks in fair locks, which can be regarded as leaving a way behind. After all, the performance of unfair ways is higher.
ReentrantLock
public class ReentrantLock implements Lock, java.io.Serializable {
private final Sync sync;
//Default unfair lock
public ReentrantLock() {
sync = new NonfairSync();
}
public ReentrantLock(boolean fair) {
sync = fair ? new FairSync() : new NonfairSync();
}
//Acquisition locks, unfair locks and fair locks are implemented differently. Look specifically at the following methods
public void lock() {
sync.lock();
}
public void lockInterruptibly() throws InterruptedException {
sync.acquireInterruptibly(1);
}
//Attempting to acquire locks in an unfair way calls the methods in Sync (whether fair or unfair). My understanding is that it is possible to give fair locks an unfair access to locks. Even in a fair lock, if the current lock is idle, the lock is immediately acquired, regardless of whether there are threads waiting in the lock's waiting queue.
public boolean tryLock() {
return sync.nonfairTryAcquire(1);
}
//This method actually calls the tryAcquire() method in each lock. Unlike the tryLock() method above, it can guarantee fairness.
public boolean tryLock(long timeout, TimeUnit unit)
throws InterruptedException {
return sync.tryAcquireNanos(1, unit.toNanos(timeout));
}
public void unlock() {
sync.release(1);
}
public boolean isHeldByCurrentThread() {
return sync.isHeldExclusively();
}
public boolean isLocked() {
return sync.isLocked();
}
//Only a few key approaches have been retained.
......
public String toString() {
Thread o = sync.getOwner();
return super.toString() + ((o == null) ?
"[Unlocked]" :
"[Locked by thread " + o.getName() + "]");
}
}
Sync (Defines the way fair locks and unfair locks are implemented)
Inheriting AQS, we define some basic methods, such as lock(), and then implement them by fair lock and unfair lock, respectively.
abstract static class Sync extends AbstractQueuedSynchronizer {
private static final long serialVersionUID = -5179523762034025860L;
//Subclasses are implemented separately
abstract void lock();
//For both lock tryLock() methods, this method is called. tryLock() is actually an attempt to acquire the lock. That is, when calling this method, CAS is used to obtain the lock to see if it is successful or false if it is not.
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()) {
//Guarantee reentry, state+1 for reentry success
int nextc = c + acquires;
if (nextc < 0) // overflow
throw new Error("Maximum lock count exceeded");
setState(nextc);
return true;
}
return false;
}
//Attempt to release, because it is reentrant, so the lock may not be released after the call, so it needs to be judged by state (state can be said to record reentrant times).
protected final boolean tryRelease(int releases) {
int c = getState() - releases;
if (Thread.currentThread() != getExclusiveOwnerThread())
//If the unlocked lock is not the lock currently occupied, there must be a mistake.
throw new IllegalMonitorStateException();
boolean free = false;
if (c == 0) {//If the state = 0, the lock is completely released after this release, and then free=true indicates that the lock is free.
free = true;
setExclusiveOwnerThread(null);
}
setState(c);
return free;
}
protected final boolean isHeldExclusively() {
return getExclusiveOwnerThread() == Thread.currentThread();
}
// Methods relayed from outer class
final Thread getOwner() {
return getState() == 0 ? null : getExclusiveOwnerThread();
}
final int getHoldCount() {
return isHeldExclusively() ? getState() : 0;
}
final boolean isLocked() {
return getState() != 0;
}
}
Next, the implementation of unfair locks
Unfair Lock NonfairSync
//Unfair Lock
static final class NonfairSync extends Sync {
private static final long serialVersionUID = 7316153563782823691L;
//An unfair lock is acquired by CAS when a thread calls Lock method instead of queue, which improves efficiency. If acquisition fails, the acquire method of AQS is called. In fact, the acquire method of AQS calls tryAcquire() implemented by NonfairSync itself.
final void lock() {
if (compareAndSetState(0, 1))
setExclusiveOwnerThread(Thread.currentThread());
else
acquire(1);
}
protected final boolean tryAcquire(int acquires) {
return nonfairTryAcquire(acquires);
}
}
Next is the implementation of fair locks
Fair Lock FairSync
static final class FairSync extends Sync {
//When a fair lock attempts to acquire a lock, it simply determines whether the lock is released or not, and whether there are waiting threads in the lock waiting queue, or if not, acquires the lock itself.
final void lock() {
acquire(1);
}
protected final boolean tryAcquire(int acquires) {
final Thread current = Thread.currentThread();
int c = getState();
//Only when the lock is idle can we try to get it. First, we judge whether there are threads in the waiting queue, and if not, we use CAS to get it.
if (c == 0) {
if (!hasQueuedPredecessors() &&
compareAndSetState(0, acquires)) {
setExclusiveOwnerThread(current);
return true;
}
}
//If the owner of the current lock is himself, reentry
else if (current == getExclusiveOwnerThread()) {
int nextc = c + acquires;
if (nextc < 0)
throw new Error("Maximum lock count exceeded");
setState(nextc);
return true;
}
//If neither of them is satisfied, the acquisition fails, and tryAcquire() is called in acquire() of AQS. When tryAcquire() returns to false, the current thread is added to the waiting thread queue
return false;
}
}
Combining with the code, we conclude that ReentrantLock is an exclusive lock, which can be re-locked, and can be divided into fair lock and unfair lock.
The difference between fair locks and unfair locks is that in unfair locks, when a thread calls lock(), it uses CAS to attempt to acquire locks (regardless of whether the lock is currently idle) and the acquisition failure is added to the queue to wait. In fair locks, when a thread calls lock(), CAS is used only when the current lock is idle and there are no other threads in the waiting queue. If there are other threads in the waiting queue, it will join the queue and acquire them sequentially. Then we can imagine that the performance of unfair locks is better than fair locks, because when a thread calls lock, the lock may just be released, so it can be occupied by the current thread. If a fair lock is used, the next waiting thread needs to be notified in the queue, and it takes a certain amount of time to wake up the thread. It is also possible that when the thread is awakened, the thread in front of the queue has run out of locks and released.
And how does reentry work? You can see that syn inherited AQS, and AQS inherited AbstractOwnable Synchronizer. In AbstractOwnable Synchronizer, there is a field for recording the thread that last acquired the lock when it was exclusively acquired.
public abstract class AbstractOwnableSynchronizer
implements java.io.Serializable {
/**
* The current owner of exclusive mode synchronization.
*/
private transient Thread exclusiveOwnerThread;
Then ReentrantLock only needs to determine whether the thread calling lock() is equal to the thread recorded in it. If it is equal, it indicates that the thread wants to acquire the lock again. ReentrantLock uses its state+1(state is actually maintained through AQS) to record the number of reentries. At release(), state-1. When state=0, it means that when the lock is released completely, other threads can get the lock.
It is also important to note that the tryLock() methods for unfair locks and fair locks are both used when the locks are available, and CAS is used to try to obtain them, ignoring the waiting queue. But for the tryLock(long timeout, TimeUnit unit) method, the respective tryAcquire() is called. So in summary, there are unfair ways to acquire locks in fair locks, which can be regarded as leaving a way behind. After all, the performance of unfair ways is higher.