“This is the 20th day of my participation in the First Challenge 2022. For details: First Challenge 2022.”

ReentrantLock source code parsing

The core of ReentrantLock is achieved through AQS and has the characteristics of AQS.

ReentrantLock Lock process

Let’s talk about this example first: the case of banking business is used to simulate how our AQS manage threads and wake up notification mechanism: Environment: there is a business window with three queuing customers; Analogy: 3 threads to simulate 3 queuing customers, and then lock the window representing the bank.

Here’s a little more intuitive:

Through this example we can debug the lock and assist understanding, next I will ReentrantLock in the specific method to analyze the lock, unlock process.

Code examples:

// How does AQS manage threads and wake up notifications
// 3 threads to simulate the bank branch, accept the window for business customers

// Customer A is the first customer. At this time, there is no one at the reception window, so A can go directly to deal with it
Lock lock = new ReentrantLock();
new Thread(() -> {
    lock.lock();
    System.out.println(" -------> A Thread come in");
    // Pause the thread for a few seconds
    try { TimeUnit.MINUTES.sleep(20); } catch(InterruptedException e) {e.printStackTrace(); } lock.unlock(); },"A").start();

// The second customer, since there is only one business window (only one thread can hold the lock), then B waits
// Enter the waiting area
new Thread(() -> {
    lock.lock();
    System.out.println(" -------> B Thread come in");
    try { TimeUnit.MINUTES.sleep(20); } catch(InterruptedException e) {e.printStackTrace(); } lock.unlock(); },"B").start();

// The third customer, since there is only one business window (only one thread can hold the lock), then C waits
// Enter the waiting area
new Thread(() -> {
    lock.lock();
    System.out.println(" -------> C Thread come in");
    try { TimeUnit.MINUTES.sleep(20); } catch(InterruptedException e) {e.printStackTrace(); } lock.unlock(); },"C").start();
Copy the code

ReentrantLock core method

The ReentrantLock method and structure are shown below, and Doug Lea is very good at it. Then there is the fair lock (FairSync), a fair lock (NonfairSync) are dependent on AbstractQueuedSynchronizer this template method.

lock()

Is the lock method, default is unfair lock.

The constructor is as follows:

The lock method looks like this:

Fair lock lock method implementation

Non-fair lock lock method implementation

A special note: the following will cover unfair/fair lock resolution, and I will say that way in bold before parsing.

acquire()

Acquire method in AbstractQueuedSynchronizer class, this method is to try to get lock, if failed, will try again, or enter the AQS queue.

public final void acquire(int arg) {
    if(! tryAcquire(arg) && acquireQueued(addWaiter(Node.EXCLUSIVE), arg)) selfInterrupt(); }Copy the code

There are three main processes (essentially three core methods) tryAcquire, addWaiter, and acquireQueued

FairSync#tryAcquire

1. The Fair lock tryAcquire is implemented by the subclass FairSync, which differs from the unfair lock by calling the Hasqueued24 method to determine whether a thread is queuing.

protected final boolean tryAcquire(int acquires) {
    final Thread current = Thread.currentThread();
    int c = getState();
    if (c == 0) {
            
        if (
            // HasqueuedToraise here is the manifestation of a fair lock. If a person is in line, he cannot obtain the lock and needs to enter the queue first, so it is better than an unfair lock in terms of performance.! hasQueuedPredecessors() &&// CAS obtains the lock
            compareAndSetState(0, acquires)) {
            // Set the lock holder
            setExclusiveOwnerThread(current);
            return true; }}/ / reentrant
    else if (current == getExclusiveOwnerThread()) {
        int nextc = c + acquires;
        if (nextc < 0)
            throw new Error("Maximum lock count exceeded");
        setState(nextc);
        return true;
    }
    return false;
}
Copy the code

2. The addWaiter operation, as the name implies, means that the current Thread fails to acquire the lock, and then wraps the current Thread into an AQS Node and enters the waiting queue

private Node addWaiter(Node mode) {
    Node node = new Node(Thread.currentThread(), mode);
    // Try the fast path of enq; backup to full enq on failure
    Node pred = tail;
    if(pred ! =null) {
        node.prev = pred;
        if (compareAndSetTail(pred, node)) {
            pred.next = node;
            // Return through this
            returnnode; }}// Enter for the first time
    enq(node);
    return node;
}
Copy the code

If there is no tail node, enQ is called to initialize the head node and enqueue:

private Node enq(final Node node) {
    for (;;) {
        Node t = tail;
        // Initialization for the first time
        if (t == null) { // Must initialize
            if (compareAndSetHead(new Node()))
                tail = head;
        } else {
            // Add the current node
            node.prev = t;
            if (compareAndSetTail(t, node)) {
                t.next = node;
                returnt; }}}}Copy the code

3. The acquireQueued method is executed after the current thread acquires the lock and before it blocks, making a final “salvage” attempt to acquire the lock before entering the wait. Another logic is that after AQS wake up, the current node that acquired the lock becomes the head node, and the “old” head node is discarded.

final boolean acquireQueued(final Node node, int arg) {
    boolean failed = true;
    try {
        boolean interrupted = false;
        for (;;) {
            final Node p = node.predecessor();
            if (p == head && tryAcquire(arg)) {
                setHead(node);
                p.next = null; // help GC
                failed = false;
                return interrupted;
            }
            if (shouldParkAfterFailedAcquire(p, node) &&
                parkAndCheckInterrupt())
                interrupted = true; }}finally {
        if(failed) cancelAcquire(node); }}Copy the code

NonfairSync#tryAcquire()

1, this time the non-fair lock NonfairSync, non-fair lock compared to fair lock there is no queuing judgment whether there is a queue, as long as the lock is acquired by the participants, can be directly locked competition.

// First look at the 'tryAcquire()' method
protected final boolean tryAcquire(int acquires) {
    return nonfairTryAcquire(acquires);
}

// The core is called 'nonfairTryAcquire(acquires)'
final boolean nonfairTryAcquire(int acquires) {
    final Thread current = Thread.currentThread();
    int c = getState();
    // If the lock can be obtained
    if (c == 0) {
        / / modify state
        if (compareAndSetState(0, acquires)) {
            // Set the owner of the lock (hold thread)
            setExclusiveOwnerThread(current);
            return true; }}// Reentrant support
    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;
}
Copy the code

AddWaiter (node. EXCLUSIVE) Implement the addWaiter method

The enQ method is called if there is no queue before

In a bidirectional list, the first node is a virtual node (also known as a sentinel node), which does not store any information. It is just a placeholder. The real first node with data starts from the second node.

acquireQueued(addWaiter(Node.EXCLUSIVE))

final boolean acquireQueued(final Node node, int arg) {
    boolean failed = true;
    try {
        boolean interrupted = false;
        / / loop
        for (;;) { 
            final Node p = node.predecessor();
            if (p == head && tryAcquire(arg)) {
                // The sentry node exits the queue
                setHead(node);
                p.next = null; // help GC
                failed = false;
                return interrupted;
            }
            / / shouldParkAfterFailedAcquire first returns false
            // Return false the second time
            if (shouldParkAfterFailedAcquire(p, node) &&
                // Internally blocks the thread to call locksupport. park(this);
                parkAndCheckInterrupt())
                interrupted = true; }}finally {
        if(failed) cancelAcquire(node); }}/ / setHead method
// Set the current lock node to the sentinel node, and set the old sentinel node to null
private void setHead(Node node) {
    head = node;
    node.thread = null;
    node.prev = null;
}

Copy the code

node.predecessor(); Method to get the former node. As we have seen before, aQS must be initialized, usually not null.

final Node predecessor(a) throws NullPointerException {
    Node p = prev;
    if (p == null)
        throw new NullPointerException();
    else
        return p;
}
Copy the code

shouldParkAfterFailedAcquire()

ShouldParkAfterFailedAcquire (p, node) method, a simple speak is to [node] effective precursors (effective) refers to the node is not CANCELLED, and set the state of the effective precursors to SIGNAL, After that, returning true means it is ready to block.

    private static boolean shouldParkAfterFailedAcquire(Node pred, Node node) {
        // Get the status of the precursor node
        int ws = pred.waitStatus;
        // If the state is SIGNAL, that is, waiting for occupied resources to be released, return true
        // Ready to continue calling the parkAndCheckInterrupt method
        if (ws == Node.SIGNAL)
            return true;
        // Ws > 0 indicates cancelled
        if (ws > 0) {
            // Loop to check whether the node's predecessor is cancelled and ignore it to re-link to the queue
            do {
                node.prev = pred = pred.prev;
            } while (pred.waitStatus > 0);
            pred.next = node;
        } else {
            // Set the precursor node of the current node to SIGNAL state for subsequent wake up operations
            // The program returns false the first time it executes here, and then loops through the outer loop a second time, finally returning from line 7
            compareAndSetWaitStatus(pred, ws, Node.SIGNAL);
        }
        return false;
    }
Copy the code

parkAndCheckInterrupt(),

ParkAndCheckInterrupt () blocks the current thread. When a thread fails to acquire the lock for several times, it enters the AQS blocking queue and waits for the holder to release resources and activate the current thread.

private final boolean parkAndCheckInterrupt(a) {
    // The thread is suspended and the program will not continue to cherish I to you
    LockSupport.park(this);

    // According to the park method API, the following three cases of the program continue to execute
    // 1. Call unpark
    // 2.
    // 3. Other illogical returns continue down
    
    // Because of the above three cases of program execution so far, return the current thread interrupt state, and clear the interrupt state
    // This method returns true if it is interrupted
    return Thread.interrupted();
}
Copy the code

Unlock method

An unfair lock is used to unlock the account

1, Unlock method source code, internal call release method and pass a 1, actually can be interpreted to return/release a lock.

The release method has two operations: The first step is to execute the tyRelease method to actually unlock, and return false if the unlock fails. The second step is to unlock successfully. It determines whether there are waiting nodes (i.e. waiting threads) in the AQS and, if so, calls the first waiting thread in the unpark queue.

The tryRelease method gets the state variable and releases the unlocked value using getState() -releases. If the current lock is not owned by the current thread, it should be released: IllegalMonitorStateException exception; After the lock is unlocked, state == 0 can be interpreted as “fully unlocked” (actually this is relative to lock reentrant). If fully unlocked, the thread holder of the current lock is cleared. Then change the value of state through CAS. Finally returns the Boolean value after the lock. One small detail is that booleans unlocked only return true if state = 0.

protected final boolean tryRelease(int releases) {
    // state -1
    int c = getState() - releases;
    if(Thread.currentThread() ! = getExclusiveOwnerThread())throw new IllegalMonitorStateException();
    boolean free = false;
    if (c == 0) {
        free = true;
        // Clear the lock holder
        setExclusiveOwnerThread(null);
    }
    // Cas state is changed
    setState(c);
    return free;
}
Copy the code

4, unparkSuccessor (h); The method is mainly to wake up the nodes in AQS. One parameter it accepts is the node node passed in the second step, which is actually the AQS rival node. It’s usually the next node after the head node, and if it doesn’t find it, it looks forward through the tail node. Finally, the queuing node S that needs to be woken up executes locksupport.unpark (s.read) to enter the logic of the previous lock grab.

    private void unparkSuccessor(Node node) {
        /* * If status is negative (i.e., possibly needing signal) try * to clear in anticipation of signalling. It is OK if this * fails or if status is changed by waiting thread. */
        int ws = node.waitStatus;
        if (ws < 0)
            compareAndSetWaitStatus(node, ws, 0);

        /* * Thread to unpark is held in successor, which is normally * just the next node. But if cancelled or apparently null, * traverse backwards from tail to find the actual * non-cancelled successor. */
        Node s = node.next;
        if (s == null || s.waitStatus > 0) {
            s = null;
            for(Node t = tail; t ! =null&& t ! = node; t = t.prev)if (t.waitStatus <= 0)
                    s = t;
        }
        if(s ! =null)
            // Queue node wake up
            LockSupport.unpark(s.thread);
    }
Copy the code

Already concluded

Flowchart sorting:

In fact, when I first looked at this, things were a little bit messy, so you can do it in your own wayReentrantLockTry to analyze the source code, to gradually clear and master.

The resources

  • docs.oracle.com/javase/8/
  • Blog.csdn.net/sunxianghua…
  • Blog.csdn.net/anlian523/a…