Already profile

ReentrantLock is an explicit lock introduced by Java in JDK1.5, which is different from synchronized in both implementation principle and function. At the end of this article, we will compare these two locks. First of all, we need to know that ReentrantLock is based on AQS, so we need to have a better understanding of AQS to learn and master ReentrantLock. For the introduction of AQS, you can refer to my previous article “Guide you to master AQS quickly”. Here is a brief review of AQS.

AQS review

AQS is the abbreviation of AbstractQueuedSynchronizer, this is an internal implementation the abstract class two queues, respectively is synchronous queue queue and conditions. The synchronous queue is a bidirectional list of waiting threads waiting to wake up to acquire the lock, while the conditional queue is a one-way list of waiting threads waking up to join the end of the synchronous queue. What AQS does is manage the wait state – wake up – between the threads in the two queues. In the synchronous queue, there are two modes, namely exclusive mode and shared mode. The difference between the two modes lies in whether AQS delivers wake up when waking up thread nodes. These two modes correspond to exclusive lock and shared lock respectively. AQS is an abstract class, so it cannot be instantiated directly. When we need to implement a custom lock, we can inherit AQS and rewrite the way to acquire locks and release locks and manage state. ReentrantLock is a lock and unlock that overwrites AQS tryAcquire and tryRelease methods.

Already the principle

ReentrantLock ReentrantLock ReentrantLock ReentrantLock ReentrantLock ReentrantLock ReentrantLock ReentrantLock ReentrantLock ReentrantLock

Already structure

ReentrantLock
Lock
3
Sync
AQS
Sync
Fair locks and unfair locks



Sync
tryAcquire
tryRelease
ReentrantLockImplementation isAQSThe exclusive lock is a pessimistic lock

ReentrantLock has an important member variable:

private final Sync sync;
Copy the code

This variable is used to refer to a subclass of Sync, i.e. FairSync or NonfairSync. The polymorphic superclass reference refers to a subclass.

public ReentrantLock() {
    sync = new NonfairSync();
}

public ReentrantLock(boolean fair) {
    sync = fair ? new FairSync() : new NonfairSync();
}
Copy the code

ReentrantLock has two constructors. The no-argument constructor defaults to creating an unfair lock, while the constructor passing true as an argument creates a fair lock.

The realization principle of unfair lock

When we use the no-argument constructor ReentrantLock lock = new ReentrantLock(), we create an unfair lock.

public ReentrantLock() { sync = new NonfairSync(); } // or pass infalsePublic ReentrantLock(Boolean fair) {sync = fair? new FairSync() : new NonfairSync(); }Copy the code

The lock method retrieves the lock

  1. lockThe method callCASMethods set upstateThe value of, ifstateIs equal to the expected value0(indicates that the lock is not occupied), then thestateUpdated to1(indicating that the thread successfully acquired the lock), and then executesetExclusiveOwnerThreadMethod directly sets the thread as the owner of the lock. ifCASSet up thestateThe value of failed, i.estateIs not equal to0, indicating that the lock is being occupiedacquire(1), the following steps.
  2. nonfairTryAcquireMethod first callsgetStateMethods to obtainstateThe value of, ifstateThe value of0The thread that occupied the lock just released the lockCASThis is astateOn success, the thread is set as the owner of the lock, and returnstrue. ifstateThe value of the0, it iscallgetExclusiveOwnerThreadMethod to check whether the thread holding the lock is itselfIf so, just go aheadstate + 1And then returntrue. ifstateDon’t for0And the owner of the lock is not oneself, then returnfalse.The thread then enters the synchronization queue.

final void lock() {//CAS operation sets the value of stateif(compareAndSetState(0, 1)) // Set the lock owner directly to the current thread processsetExclusiveOwnerThread(Thread.currentThread());
    elseAcquire (1); } public final void acquire(int arg) {// Call the tryAcquire method overridden by subclasses if tryAcquire returnsfalseThe thread will enter the synchronization queueif(! tryAcquire(arg) && acquireQueued(addWaiter(Node.EXCLUSIVE), arg)) selfInterrupt(); } protected final Boolean tryAcquire(int acquires) {// Call nonfairTryAcquirereturnnonfairTryAcquire(acquires); } final boolean nonfairTryAcquire(int acquires) { final Thread current = Thread.currentThread(); int c = getState(); // If state=0, i.e., the lock was released by the lock owner during that time, then state=0if(c == 0) {// Use the CAS operation to set the value of stateif(compareAndSetState(0, acquires)) {// The owner of the lock is set to the current thread and returnstrueThat is, the current thread will not enter the synchronous // queue.setExclusiveOwnerThread(current);
            return true; }} // If the state is not equal to 0, that is, a thread is holding the lock, then check if the thread is itselfelse if(current == getExclusiveOwnerThread()) {// If the thread is itself, return state+1trueYou don't need to get the lock anymore because the lock is already on you. int nextc = c + acquires;if (nextc < 0) // overflow
            throw new Error("Maximum lock count exceeded");
        setState(nextc);
        return true; } // If state is not equal to 0 and the lock is not owned by the thread, the thread will enter the synchronization queue.return false;
}
Copy the code

Release the tryRelease lock

  1. Determine if the current thread is the owner of the lock and proceed with the step if so2If not, an exception is thrown.
  2. After determining the lock releasestateIs the value of 0, and if soThe lock is not reentrantAnd then set the lock owner tonullAnd returntrueThen perform the steps3If not, thenRepresents that the lock has been reentrantstep4.
  3. Now the lock has been released, i.estate=0Wake up the subsequent nodes in the synchronization queue to acquire the lock.
  4. The lock has not been released, i.estate! = 0Does not wake up the synchronization queue.

public void unlock() { sync.release(1); } public final Boolean release(int arg) {public final Boolean release(int arg)true// Its thread is awakened to try to acquire the lock.if (tryRelease(arg)) {
        Node h = head;
        if(h ! = null && h.waitStatus ! = 0) unparkSuccessor(h);return true;
    }
    return false; } protected final Boolean tryRelease(int releases) {// State of the state minus releases int c = getState() -releases; // Determine whether the lock is owned by the threadif(Thread.currentThread() ! = getExclusiveOwnerThread ()) / / if the owner is not the thread throw an exception Is the lock release is the premise of threads with the lock, throw new IllegalMonitorStateException (); boolean free =false; // If the thread releases the lock and the state is 0, that is, the lock is not reentrant, then the owner of the lock is set to NULL and returnstrueThat means you can wake up another thread to acquire the lock. If state does not equal 0 after the thread releases the lock, then the lock is reentrantfalse, indicating that the lock is not being released and no other thread needs to be woken up.if (c == 0) {
        free = true;
        setExclusiveOwnerThread(null);
    }
    setState(c);
    return free;
}
Copy the code

The implementation principle of fair lock

The lock method retrieves the lock

  1. Get statestateThe value of, ifstate=0That is, the lock is not occupied by another thread (but does not mean that there are no threads waiting on the synchronization queue)2. ifstate! = 0The lock is being held by another thread3.
  2. Check whether there are threads (nodes) in the synchronization queue, if not, set the lock owner to the current thread, update the state, and return true.
  3. Determine if the lock is owned by the current thread, update the value of state if it is, and return true if it is not, return false, that is, the thread will be added to the synchronization queue

The fairness of lock acquisition is realized through Step 2, that is, the lock is acquired in the first come, first served order, and the later one cannot get the lock first. Unfair lock and fair lock are also realized through this difference.

final void lock() { acquire(1); } public final void acquire(int arg) {public final void acquire(int arg) {public final void acquire(int arg) {if(! tryAcquire(arg) && acquireQueued(addWaiter(Node.EXCLUSIVE), arg)) selfInterrupt(); } protected final boolean tryAcquire(int acquires) { final Thread current = Thread.currentThread(); int c = getState(); // Check whether the state is 0, 0 indicates that the lock is not occupied, not 0 indicates that the lock is occupied.if(c == 0) {// Call the HasqueuedToraise method to determine whether a thread is waiting on the synchronous queue. If no thread is waiting on the synchronous queue, the current thread becomes the owner of the lock. // This mechanism is a fair lock mechanism, that is, the first thread to acquire the lock, the later can not be the first to acquire the lock.if(! hasQueuedPredecessors() && compareAndSetState(0, acquires)) {setExclusiveOwnerThread(current);
            return true; }} // Determine if the current thread is the owner of the lock, if so, update the state directly, and returntrue.else if (current == getExclusiveOwnerThread()) {
        int nextc = c + acquires;
        if (nextc < 0)
            throw new Error("Maximum lock count exceeded");
        setState(nextc);
        return true; } // Return if there is a thread in the synchronization queue and the owner of the lock is not the current threadfalse.return false;
}
Copy the code

Release the tryRelease lock

Fair lock release is the same as unfair lock release, which will not be repeated here. The fairness of fair locks and unfair locks is reflected when the lock is acquired, and when the lock is released, it is released equally.

LockInterruptibly Obtains the lock interruptively

ReentrantLock has some more convenient features than Synchronized, such as the ability to interrupt to acquire the lock.

public void lockInterruptibly() throws InterruptedException { sync.acquireInterruptibly(1); } public final void acquireInterruptibly(int arg) throws InterruptedException {// If the current thread is interrupted, throw an exceptionif(Thread.interrupted()) throw new InterruptedException(); // if the current thread still failed to acquire the lockdoThe AcquireInterruptibly method, similar to the //acquireQueued method, raises an exception if the thread is interrupted while it is waiting.if(! tryAcquire(arg))doAcquireInterruptibly(arg);
}
Copy the code

TryLock Obtaining the lock in timeout waiting mode

ReentrantLock can also be used to acquire a lock in a timeout mode, where a thread returns false if it has not acquired the lock within a timeout period, rather than in an “infinite loop”.

  1. If the node has been interrupted, an exception will be thrown. If the node has not been interrupted, an attempt will be made to obtain the lockdoAcquireNanosMethod acquires the lock using a timeout wait.
  2. Add the current node encapsulated in exclusive mode to the end of the synchronization queue.
  3. Into the “endless loop,”However, there is a limit to this loop, that is, if the thread has not acquired the lock after the timeout, then it will returnfalse, end the loop. So this is going to be calledLockSupport.parkNanosMethod is not interrupted during the timeout period, then the thread will start fromThe timeout wait state changes to the ready stateAnd then beCPUScheduling continues to execute the loop,If the thread has reached the timeout threshold, return false.

LockSuport’s methods respond to Thread.Interrupt but do not throw an exception

public boolean tryLock(long timeout, TimeUnit unit)
        throws InterruptedException {
    returnsync.tryAcquireNanos(1, unit.toNanos(timeout)); } public final Boolean tryAcquireNanos(int arg, long nanosTimeout) throws InterruptedException {// Throw an exception if the current thread is interruptedif(Thread.interrupted()) throw new InterruptedException(); // Try to get it again and call if unsuccessfuldoAcquireNanos method timeout waits to acquire lockreturn tryAcquire(arg) ||
        doAcquireNanos(arg, nanosTimeout);
}

private boolean doAcquireNanos(int arg, long nanosTimeout)
        throws InterruptedException {
    if (nanosTimeout <= 0L)
        return false; Final Long deadline = system.nanotime () + nanosTimeout; Final Node Node = addWaiter(node.exclusive); // Call addWaiter to wrap the current thread as a Node in EXCLUSIVE mode and join the end of the queue. boolean failed =true;
    try {
        for(;;) { final Node p = node.predecessor(); // Let the current node try to acquire the lock if its precursor is a head node.if(p == head && tryAcquire(arg)) {// If the lock is acquired successfully, set the current node as the head node and returntrue.setHead(node);
                p.next = null; // help GC
                failed = false;
                return true; } // If the current node's precursor node is not a head node or the current node fails to acquire the lock, // the current thread is judged again to have timed out. nanosTimeout = deadline - System.nanoTime();if (nanosTimeout <= 0L)
                return false; / / call shouldParkAfterFailedAcquire method, tell the precursor of the current node I want to enter into / / wait state, to I remember calling me, namely, ready to before has reached the awaited state.if(shouldParkAfterFailedAcquire (p, node) && nanosTimeout > spinForTimeoutThreshold) / / call LockSupport parkNanos method, Sets the current thread to timeout wait state. LockSupport.parkNanos(this, nanosTimeout);if (Thread.interrupted())
                throw new InterruptedException();
        }
    } finally {
        if(failed) cancelAcquire(node); }}Copy the code

ReentrantLock’s wait/notification mechanism

We know that Synchronized + Object wait and notify and notifyAll can implement wait/notification, but ReentrantLock can also implement wait/notification. ReentrantLock implements the same semantics as Wait, notify, and notifyAll through Condition, which is a conditional queue. The thread executes the condition.await() method to move node 1 from the synchronous queue to the conditional queue.

The thread executes the conditional.signal () method to move node 1 from the conditional queue to the synchronous queue.

Because only threads in the synchronous queue can acquire locks, the wait/notification mechanism is implemented through the Condition object’s wait and signal methods. Code examples:

ReentrantLock lock = new ReentrantLock();
Condition condition = lock.newCondition();
public void await() {
    lock.lock();
    try {
        System.out.println("Thread acquires lock ----"+ Thread.currentThread().getName()); condition.await(); // Calling the await() method releases the lock, just as object.wait () does. System.out.println("Thread awakened ----" + Thread.currentThread().getName());
    } catch (InterruptedException e) {
        e.printStackTrace();
    } finally {
        lock.unlock();
        System.out.println("Thread release lock ----" + Thread.currentThread().getName());
    }
}

public void signal() { try { Thread.sleep(1000); } catch (InterruptedException e) {e.printStackTrace(); } lock.lock(); try { System.out.println("Another thread acquires the lock ----" + Thread.currentThread().getName());
        condition.signal();
        System.out.println("Wake up thread ----" + Thread.currentThread().getName());
    } finally {
        lock.unlock();
        System.out.println("Another thread releases lock ----" + Thread.currentThread().getName());
    }
}

public static void main(String[] args) {
    Test t = new Test();
    Thread t1 = new Thread(new Runnable() {
        @Override
        public void run() { t.await(); }}); Thread t2 = new Thread(newRunnable() {
        @Override
        public void run() { t.signal(); }}); t1.start(); t2.start(); }Copy the code

Run output:

---- thread-0 Another Thread obtains the lock ---- thread-1 awakens the Thread ---- thread-1 Another Thread releases the lock ---- thread-1 is awakens the Thread ---- thread-0 releases the lock and the Thread is freeCopy the code

Thread T1 obtains the lock and outputs “Thread obtains the lock —- thread-0”. Then Thread T1 calls the await method. The result of calling this method is that Thread T1 releases the lock and enters the wait state, waiting to wake up. The result of calling the signal method is to wake up a Thread in the Condition. T1 is then woken up. T2 has not released the lock, and t1 is unable to acquire the lock. Thread T2 releases the lock and outputs “another Thread releases the lock —- thread-1”, at which point Thread T1 obtains the lock and continues to execute the output: —- thread-0, Then release the lock and print “Thread release lock —- thread-0”.

What do you do if you want to wake up part of the thread separately? This makes it necessary to use multiple Condition objects, because ReentrantLock supports creating multiple Condition objects, such as:

ReentrantLock lock = new ReentrantLock(); Condition condition = lock.newCondition(); Condition condition1 = lock.newCondition(); // Thread 1 calls the condition.await() thread to enter the condition queue condition.await(); Condition1.await (); // Thread 2 calls condition1.await(). Condition.signal () from thread 32 only wakes up the thread in condition and does not affect condition1. condition1.await();Copy the code

In this way, the function of partial awakening is realized.

ReentrantLock compared with Synchronized

The introduction of Synchronized can be seen in the use of Synchronized (I), in-depth Analysis of the principle of Synchronized and lock Expansion process (II)

ReentrantLock Synchronized
The underlying implementation throughAQSimplementation throughJVMImplementation, wheresynchronizedThere are multiple types of locks, except heavyweight locks that are passedmonitorObject (the operating system mutex mutex primitive), other types are implemented through object headers.
Whether it is reentrant is is
Fair lock is no
Not fair lock is is
The type of lock Pessimistic locks, explicit locks Pessimistic locks, implicit locks (built-in locks)
Whether interrupt is supported is no
Whether timeout wait is supported is no
Whether to automatically obtain/release locks no is

reference

“The art of concurrent Java programming” understanding AbstractQueuedSynchronizer (AQS) Java reentrant lock already principle analysis)

Original address: ddnd.cn/2019/03/24/…