I wrote @reentranctLock

JAVA concurrency version 1.8


  • I wrote @reentranctLock
    • 1. ReentranctLock definition
      • 1.1 ReentranctLock review
      • 1.2 Best Practices
    • 2. ReentranctLock composition
      • 2.1 the class definition
      • 2.2 the constructor
      • 2.3 Important Variables
      • 2.4 the lock method
      • 2.5 Other Methods
      • 2.6 Conditional Queues
    • 3. Sync-sync
      • 3.1 the class definition
      • 3.2 Core methods
        • 3.2.1 the lock
        • 3.2.2 nonfairTryAcquire
        • 3.2.3 tryRelease
      • 3.3 Important Methods
    • 4.NonfairSync – Unfair lock
      • 4.1 the class definition
      • 4.2 Core Methods
        • 2 the lock
        • 4.2.2 tryAcquire
    • 5.FairSync – Fair lock
      • 5.1 the class definition
      • 5.2 Core Methods
        • 5.2.1 the lock
        • 5.2.2 tryAcquire
  • Note: Since there are too many English annotations in the JDK documentation, I choose to extract some of the most important ones, and the rest will be presented in Chinese annotations
  • Recommendation: I suggest readers combine with @synchronized article (version 1.8) to understand
  • Recommendation: the author suggests readers combined with concurrent @ AbstractQueuedSynchronizer is through understanding
  • Thank you for your support: kiraSally’s personal blog for the Nuggets

1. ReentranctLock definition

1.1 ReentranctLock review

ReentrantLock is an exclusive mutual-exclusive ReentrantLock provided in Synchronized packets, and its extensibility can be found in comparison with Synchronized:
Different lock implementation mechanisms:
ReentrantLockThe underlying implementation relies on AQS(CAS+CLH), which is distinct from the Syn monitor pattern
Lock acquisition is more flexible:
ReentrantLockInterrupts, timeouts, and attempts to acquire locks are more flexible than Syn
Different lock release forms:
ReentrantLockMust show the call
unlock()The lock is released, and Syn automatically releases the monitor
Fair strategy support:
ReentrantLockBoth fair and unfair policies are provided to support both ordered or high-throughput execution, and the Syn itself is an unfair lock
Conditional queues support:
ReentrantLockIs an exclusive mode implementation based on AQS, so it also provides support for conditional queues in the form of pipes, whereas Syn does not
Reentrant support:
ReentrantLockThe reentrant effect of Syn is the same as that of Syn, except that it automatically releases the lock

Q: What is reentrant? A friendly note: a friend of mine asked me what reentrant is, and I thought I needed to tell you again

Reentrant means that a thread can acquire and release an exclusive lock multiple times. Reentrant to Synchronized is similar to “wrapping a large box on top of a small one while packaging, and opening a small one after opening the large one”, i.e., unlocking in the sequence of locking


1.2 Best Practices

Class X {//1. instantiate a ReentrantLock object Private final ReentrantLock lock = new ReentrantLock(); Public void m() {//2. Lock - block until lock lock.lock(); try { //3.doSomething... } finally {//4. Release the lock - Must be unlocked every time in finally! lock.unlock() } } } }Copy the code

2. ReentranctLock composition

2.1 the class definition

public class ReentrantLock implements Lock, java.io.SerializableCopy the code

2.2 the constructor

Public ReentrantLock() {sync = new NonfairSync(); /** * Public ReentrantLock() {sync = new NonfairSync(); } /** * Optional fair policy constructor * @param fair true: fair false: unfair policy */ public ReentrantLock(Boolean fair) {sync = fair? new FairSync() : new NonfairSync(); }Copy the code

2.3 Important Variables

/** Synchronizer provides all implementation methods, note that sync instances are immutable once generated - default is not fair */ private final sync sync;Copy the code

2.4 the lock method

/** * public void lock() {sync.lock(); Acquiring a lock the * * *} / response to an interrupt * / public void lockInterruptibly () throws InterruptedException {sync. AcquireInterruptibly (1); } /** * public Boolean tryLock() {return sync.nonfairtryacquire (1); } /** * Public Boolean tryLock(long timeout, long timeout) TimeUnit unit) throws InterruptedException { return sync.tryAcquireNanos(1, unit.toNanos(timeout)); } /** * unlock */ public void unlock() {sync.release(1); }Copy the code

2.5 Other Methods

/** * public int getHoldCount() {return sync.getholdCount (); } /** * Check whether the thread holding the lock is the current thread. */ public Boolean isHeldByCurrentThread() {return sync.isheldexclusively (); } public Boolean isLocked() {return sync. IsLocked ();} public Boolean isLocked() {return sync. } /** * Public final Boolean isFair() {return sync instanceof FairSync; } /** * Check whether the synchronization queue is not empty * note: Even if it returns true, Public Final Boolean hasQueuedThreads() {return public Final Boolean hasQueuedThreads() {return sync.hasQueuedThreads(); } /** * Check whether the specified thread is in the synchronization queue * note: Even if it returns true, Public final Boolean hasQueuedThread(Thread Thread) {return public final Boolean hasQueuedThread(Thread Thread) {return sync.isQueued(thread); } /** * get the number of nodes in the synchronization queue * note: Public final int getQueueLength() {return sync.getQueuelength (); public final int getQueueLength(); }Copy the code

2.6 Conditional Queues

Public Condition newCondition() {return sync.newcondition (); } /** * Check whether the specified conditional queue is not empty * note: Even if it returns true, Public Boolean hasWaiters(Condition Condition) {if the node loves its work as a monitoring method */ Public Boolean hasWaiters(Condition Condition) {if the node loves its work as a monitoring method (condition == null) throw new NullPointerException(); if (! (condition instanceof AbstractQueuedSynchronizer.ConditionObject)) throw new IllegalArgumentException("not owner"); return sync.hasWaiters((AbstractQueuedSynchronizer.ConditionObject)condition); } /** * Get the number of nodes in the conditional queue * note: This value is just an estimate, Public int getWaitQueueLength(Condition Condition) {if (Condition == null) throw new NullPointerException(); if (! (condition instanceof AbstractQueuedSynchronizer.ConditionObject)) throw new IllegalArgumentException("not owner"); return sync.getWaitQueueLength((AbstractQueuedSynchronizer.ConditionObject)condition); }Copy the code

3. Sync-sync

3.1 the class definition

/** * Base of synchronization control for this lock. Subclassed * into fair and nonfair versions below. Uses AQS state to * represent the number of holds on the lock. * * 1. Synchronization control for reentrant locks is implemented based * 2. It provides base support for fair and unfair versions * 3. It uses the state field of AQS to describe the number of times the thread holds the lock, so it inherits AQS * note: The realization of the locking mechanism is based on AQS, so readers must first understand the AQS * / abstract static class Sync extends AbstractQueuedSynchronizerCopy the code

Q: Why is Sync a static abstract inner class? A friendly tip: Although this one is relatively simple, thinking about it can help us optimize the code

Let’s think about it
Static inner,
abstractAnd think about it in combination with the TEMPLATE mode of AQS:
Static inner class: A static inner class tells the reader that its inner class does not need to depend on an instance of an external class, which is equivalent to a static variable member
Abstract: The main purpose of abstraction here is to communicate with
LockOf the interface
lock()Methods remain consistent, extending the flexibility of custom implementations
Template pattern: The AQS class is itself an abstract class that defines many methods for subclasses to implement, while
SyncIs a subclass of it; Meanwhile, attentive readers will find that there are other concurrent classes with the same name
SyncClass implementation, such as
CountDownLatchSo static internal also has the role of a namespace, is also a variant of the template pattern implementation

3.2 Core methods

3.2.1 the lock

/** * Performs {@link Lock#lock}. The main reason for subclassing * is to allow fast path for nonfair version. * * This abstract method is used to keep the Lock () method of the Lock interface consistent */ abstract void Lock () is provided to subclass implementations to provide a quick way to pass unfair versions */ abstract void Lock ();Copy the code

3.2.2 nonfairTryAcquire

Locking does two things:
1.CAS updates the status (the status value will decrease and increase)
2. Set the thread associated with the lock
/** * Performs non-fair tryLock. tryAcquire is implemented in * subclasses, * @param acquires Specifies the number of locks to be acquired */ final Boolean nonfairTryAcquire(int acquires) { //1. Get the currentThread - Note that final Thread current = thread.currentThread () is used to ensure that references remain unchanged; Int c = getState(); If (c == 0) {/** * 4.CAS updates the lock status * - Because there may be concurrency between getState and update operations, CAS must be used * - If CAS fails, */ if (compareAndSetState(0, acquires)) {//5. In addition to updating the CAS state, another key step in ensuring that the thread holds the lock is to set the lock association thread setExclusiveOwnerThread(current); //6. Return true when the lock is successfully held; Else if (current == getExclusiveOwnerThread()) {//8 Int nexTC = c + acquires; if (nextc < 0) // overflow throw new Error("Maximum lock count exceeded"); /** * setState(nexTC); /** * setState(nexTC); //10. Reentrant lock returns true immediately; } //11. Return false; }Copy the code

3.2.3 tryRelease

Unlocking does two things:
1.CAS updates the status (the status value is reduced)
2. Remove the association between the lock and the current thread

Note: In reentrant cases tryRelease may return false while the lock is still being released

/** * Release lock - Note support for reentrant * @param acquires The number of locks that you want to release */ protected Final Boolean tryRelease(int Releases) {/** * 1. */ int c = getState() -releases; //2. Note: only the thread holding the lock can release the lock!! If (thread.currentThread ()! = getExclusiveOwnerThread()) throw new IllegalMonitorStateException(); Return true */ Boolean free = false; return true */ Boolean free = false; if (c == 0) { free = true; setExclusiveOwnerThread(null); } /** * setState(c);} /** * setState(c); //5. True return free is returned only when the reentrant lock is fully released; }Copy the code

3.3 Important Methods

/** * Final Boolean isLocked() {return getState()! = 0; Thread getOwner() {return getState() == 0? null : getExclusiveOwnerThread(); } /** * protected final Boolean isHeldExclusively() {return getExclusiveOwnerThread() == Thread.currentThread(); } /** * Final int getHoldCount() {return isHeldExclusively()? getState() : 0; ConditionObject newCondition() {return new ConditionObject(); / / ConditionObject newCondition(); }Copy the code

4.NonfairSync – Unfair lock

Unfair lock: When adding a lock, you do not need to consider whether there is a thread waiting. If the lock fails to be acquired, it is automatically appended to the end of the synchronization queue

4.1 the class definition

static final class NonfairSync extends SyncCopy the code

4.2 Core Methods

2 the lock

/** * Performs lock. Try immediate barge, Backing up to normal * acquire on failure. */ final void lock() {/** * 1. */ if (compareAndSetState(0, 1)) //2) */ if (compareAndSetState(0, 1)) //2) SetExclusiveOwnerThread (thread.currentThread ()); Acquire (1); acquire(1); }Copy the code

4.2.2 tryAcquire

/** * This method is a subclass implementation of the AQS abstract method tryAcquire(), * - The non-fair policy calls Sync's nonfairTryAcquire() method * @param acquires The number of locks to acquire */ protected Final Boolean tryAcquire(int acquires) { return nonfairTryAcquire(acquires); }Copy the code

5.FairSync – Fair lock

Fair lock: Lock money needs to check if there are still threads queuing (waiting), priority queuing

5.1 the class definition

static final class FairSync extends SyncCopy the code

5.2 Core Methods

5.2.1 the lock

/** * Get lock - fair version * - This method is a subclass of Sync's abstract method Lock () method * - this method calls AQS aquire() method directly */ final void lock() {// get one resource at a time acquire(1); }Copy the code

5.2.2 tryAcquire

/** * Try to acquire the lock - fair version * - This method is a subclass of the AQS abstract tryAcquire() method, mainly used by AQS * - The lock can only be obtained if the recursive call (end), there is no wait or the first wait, * simply there is no one in front, * @param acquires Number of locks */ protected Final Boolean tryAcquire(int acquires) {//1. Get the currentThread - Note that final Thread current = thread.currentThread () is used to ensure that references remain unchanged; Int c = getState(); If (c == 0) {/** * 4. An important difference from nonfairTryAcquire! * - The current thread is eligible to acquire the lock only if there are no other queuers in front of it * - Fairness is not a direct competition, but a check to see if there are any queued nodes before it */ if (! hasQueuedPredecessors() && compareAndSetState(0, acquires)) { setExclusiveOwnerThread(current); return true; Else if (current == getExclusiveOwnerThread()) {//6. Int nexTC = c + acquires; if (nextc < 0) throw new Error("Maximum lock count exceeded"); /** * setState(nexTC); /** * setState(nexTC); //8. Reentrant lock returns true immediately; } //9. Failed to obtain the lock. } /** * If there is a thread waiting longer than the current thread in the queue * - this is equivalent to getFirstQueuedThread()! = thread.currentThread () && hasQueuedThreads() * - This method is specifically designed for fair policy, Public final Boolean hasqueued24 () {Node t = tail; public final Boolean hasqueued24 () {Node t = tail; Node h = head; Node s; // A CLH lock is a thread that is waiting for a return. = t && ((s = h.next) == null || s.thread ! = Thread.currentThread()); }Copy the code

Q: How does ReentranctLock achieve fairness? Friendly note: the key lies on the use of hasqueuedtoraise ()

Answer: Attentive readers may have noticed that inStep 4When, the && operation is used, that is, CAS must be executed after the former meets the conditions; At the same time combined withtryAquire()In AQS, it is easy to know the fair implementation, but for convenience, the author uses examples to briefly say the implementation process of AQS:

Basic elements:
There’s a variable state=0; If thread A acquires the lock once, state+1; A releases the lock once,state-1; At the same time the lock sets/clears the associated thread;
Basic process:
If thread A holds the lock, state increases and >0; Thread attempts to acquire lock B at this time, if the CAS (1 | N) time (0, 1) failure, will join synchronous queue of the thread is suspended. When A fully releases the lock, state decreases and =0, and wakes up the head node of the synchronization queue (suppose it is B at this time). After being awakened, B will try CAS(0,1). Thread C also tries to compete for the lock. There are two ways to do this:
Unfair lock implementation:
Thread C tries CAS(0,1) directly. If the update succeeds, then B fails to acquire the lock, so it needs to suspend again.
Fair lock implementation:
Thread C checks to see if there is any thread in the synchronization queue that has waited longer than the current thread. If there is, it does not execute the CAS and directly enters the queue and suspends the wait, so that THREAD B can acquire the lock.

Conclusion: So fair and not fair is the difference between a lock is released (state = 0), has not thread and have team and squad had awakened the waiting thread who first CAS between nodes (head), to perform a successful first acquiring a lock, and then add that synchronous queue itself is FIFO, so don’t make a mistake who is the object need to be fair…