Interpretation of ReentrantLock Lock Principle in JAVA Concurrent Programming

Posted by rewast on Tue, 30 Jul 2019 11:12:11 +0200

Author: All his life
Wechat: 878799579

Lock state transition

Lock classification

Jdk1.5 later helped us to provide thread synchronization mechanism to achieve synchronization between objects by displaying and defining synchronization locks. It was written by Doug Lea. I believe people who have read the source code can see this guy in many places.

Lock can be displayed to lock and unlock. But only one thread can lock Lock objects at a time.

The Lock implementation structure is shown in the following figure:

(1), (2), (3) are marked according to the usage. Next, we will mainly learn the use of <font color="red">ReentrantLock</font>.

Re-entrainable lock

The premise of implementing <font color="purple">ReentrantLock is AbstractQueued Synchronizer, short for AQS. The internal implementation of the core method is </font> in AQS. In the follow-up, we will interpret the AQS related knowledge points and use scenarios in detail. Let's start with a pseudocode to describe the use of reentrant locks. Next, we will analyze in detail what happened to the acquisition lock and the release of the lock's internal implementation.

package org.bilaisheng.juc;

import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;

/**
 * @Author: bilaisheng
 * @Wechat: 878799579
 * @Date: 2019/1/3 22:55
 * @Todo: Pseudo-code only demonstrates the use of
 * @Version : JDK11 , IDEA2018
 */
public class ReentrantLockTest {


    public static void main(String[] args) {
        Lock lock = new ReentrantLock();

        // Acquisition locks
        lock.lock();
        
        // access the resource protected by this lock
        // do something
        
        // Release lock
        lock.unlock();

    }
}

Sync Object Analysis

     /** Synchronizer providing all implementation mechanics */
    private final Sync sync;

    /**
     * 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.
     */
    abstract static class Sync extends AbstractQueuedSynchronizer {
        // xxxx
    }    

He instantiates the implementation classes FairSync and NoFairSync of Sync based on Boolean type parameters passed into the constructor.

  • FairSync: Fair Sync
  • NoFairSync: Unfair Sync
public ReentrantLock(boolean fair) {
    sync = fair ? new FairSync() : new NonfairSync();
}

Implementation Principle of ReentrantLock.lock()

ReentrantLock, which we use more often, is an unfair lock. Let's use a graph to analyze how it is implemented.

The picture above does two things:

1. Set the state of AbstractQueued Synchronizer to 1

2. Set the thread of AbstractOwnable Synchronizer as the current thread

Thread A is executing, status = 1.

Thread B tries to use CAS to determine whether the state is 0 or not, and then sets it to 1. Of course, this step must fail, because Thread A has set the state to 1, so it must fail at this time.

Failed and entered the FIFO waiting queue. Waiting for a new attempt

/**
 * 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;
}

Because state is declared in AbstractQueued Synchronizer with volatile keyword, it can be seen between threads.

/**
 * The synchronization state.
 */
private volatile int state;

Once again, determine whether you can hold a lock (thread A synchronization code may execute faster and have released the lock), and not return false.

According to the above code, it can be seen that the same lock can re-enter Integer.MAX_VALUE at most times, that is 2147483647.

Implementation Principle of ReentrantLock.unLock()

It's simpler here. Attached Call Relational Link

// Step 1
public void unlock() {
    sync.release(1);
}

// Step 2: AbstractQueued Synchronizer
public final boolean release(int arg) {
    if (tryRelease(arg)) {
        Node h = head;
        if (h != null && h.waitStatus != 0)
           unparkSuccessor(h);
        return true;
    }
    return false;
}

//Step 2: ReentrantLock
 @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;
}

When a thread unlocks the same ReentrantLock, the AQS state is zero. AbstractOwnable Synchronizer's exclusive Owner Thread will be set to null, which means that no thread holds the lock, and the method returns true.

Pay attention to me if you like it.

Topics: Java