Reference link: https://www.bilibili.com/video/BV1ta4y1H73X
AQS knowledge is required
Reentrant means that when a single thread executes, re entering the same subroutine is still thread safe.
If it is non reentrant, if A obtains A lock, it will cause A deadlock when it requests the lock again
In short, a thread can repeatedly obtain the lock n times without releasing, and respond to release n times when releasing.
Let's talk about the implementation of RenentrantLocak, a reentrant lock.
First, let's take a look at the inheritance relationship of RenentrantLocak, which implements the Lock interface, that is, it follows the abstract definition of the Lock interface.
public class ReentrantLock implements Lock, java.io.Serializable
Lock
Lock provides another synchronization operation mode different from synchronized.
More extensive operations, more flexible structures can be supported, and multiple Condition objects can be associated.
// Acquire lock void lock(); // Obtain the lock. If it is interrupted while waiting for the lock, exit the wait and throw an exception void lockInterruptibly() throws InterruptedException; // Try to acquire the lock and return immediately boolean tryLock(); // An attempt to acquire a lock is made over a period of time. If the lock is interrupted, an interrupt exception will be thrown boolean tryLock(long time, TimeUnit unit) throws InterruptedException; // Release lock void unlock(); // Create a new Condition object bound to the current Lock Condition newCondition();
Fair lock and unfair lock
Fair lock: it is allocated according to the request order of locks. For example, the FIFO mechanism in AQS implements fair locks
Unfair locks: locks are not allocated in the order in which they are requested, but there may be hunger in this way
Why design unfair locks?
In fact, the efficiency of unfair locks is often higher, which may improve the performance of concurrency.
When a thread wakes up, the thread state switching will have a short delay. The unfair lock mechanism allows the thread to preempt the lock during this event and quickly process the content.
This is one of the reasons why unfair locks perform better than fair locks.
Construction method
From the perspective of construction method, ReentrantLock is actually a Sync class. You can select FairSync and NonfairSync (default) through parameters.
public ReentrantLock() { sync = new NonfairSync(); } public ReentrantLock(boolean fair) { sync = fair ? new FairSync() : new NonfairSync(); }
attribute
final Sync sync
Sync is an internal abstract class of ReentrantLock, which inherits AQS
NonfairSync and FairSync are sub classes of Sync implementation, which are the implementation of non fair lock and fair lock respectively.
abstract static class Sync extends AbstractQueuedSynchronizer {...} static final class NonfairSync extends Sync {...} static final class FairSync extends Sync {...}
Back to Sync, you can notice that there is a nonfairTryAcquire(int acquires) method in Sync. You can know that it is a non fair lock method by name. Since it is implemented by NonfairSync and FairSync, why is this method defined in its parent class?
Let's take a look at this method:
final boolean nonfairTryAcquire(int acquires) { final Thread current = Thread.currentThread(); int c = getState(); // 0 indicates that the lock state is idle, CAS can be used to obtain the lock // If the state change is successful, the lock is obtained if (c == 0) { if (compareAndSetState(0, acquires)) { setExclusiveOwnerThread(current); return true; } } // If the lock is occupied, judge whether the current thread is the thread holding the lock else if (current == getExclusiveOwnerThread()) { // Reentry, count + 1 int nextc = c + acquires; if (nextc < 0) // overflow, that is to prevent the number of threads from overflowing and becoming negative throw new Error("Maximum lock count exceeded"); setState(nextc); return true; } // Lock not acquired return false; }NonfairSync
NonfairSync implements the unfair lock mechanism by providing two preemptions:
- Preemption during lock
- Overridden tryAcquire calls nonfairTryAcquire
static final class NonfairSync extends Sync { private static final long serialVersionUID = 7316153563782823691L; final void lock() { // Attempt to acquire lock [first preemption] if (compareAndSetState(0, 1)) // If successful, the lock is obtained setExclusiveOwnerThread(Thread.currentThread()); else // If it fails, the acquire method of AQS is called // Because the tryAcquire called in the acquire method is rewritten. // Therefore, the following trayAcquire method is called [Second preemption] acquire(1); } protected final boolean tryAcquire(int acquires) { return nonfairTryAcquire(acquires); } }FairSync
static final class FairSync extends Sync { private static final long serialVersionUID = -3000897897090466540L; final void lock() { // Direct queue operation acquire(1); } // Override tryAcquire protected final boolean tryAcquire(int acquires) { final Thread current = Thread.currentThread(); int c = getState(); // Determine whether the lock is idle if (c == 0) { // You need to determine whether there is a pre waiting node if (!hasQueuedPredecessors() && compareAndSetState(0, acquires)) { setExclusiveOwnerThread(current); return true; } } // Reentry mechanism else if (current == getExclusiveOwnerThread()) { int nextc = c + acquires; if (nextc < 0) throw new Error("Maximum lock count exceeded"); setState(nextc); return true; } return false; } }
method
lock
It has been implemented in sync. You can call it directly.
public void lock() { sync.lock(); }
trylock
public boolean tryLock() { return sync.nonfairTryAcquire(1); }
unlock
public void unlock() { sync.release(1); }