1. Create a fair lock

1. Usage

Lock reentrantLock = new ReentrantLock(true);
reentrantLock.lock(); / / lock
try{
  // todo
} finally{
  reentrantLock.unlock(); / / releases the lock
}
Copy the code

2. Create a fair lock

Add keyword true to new ReentrantLock(true)

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

The object created when the argument passed is true is **new FairSync()** fair lock.

2. The implementation of lock

1. Common fetch lock

reentrantLock.lock(); / / lock
Copy the code

The method that actually calls the lock is createdLock the inside fairlymethods

static final class FairSync extends Sync {
    final void lock(a) {
        acquire(1); }... }Copy the code

Acquire methods in code, like acquire methods in unfair locks, are final methods in the AQS called

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

The difference, however, is that the tryAcquire(ARG) method is implemented inside the calling fair lock

This method is particularly similar to the unfair lock method, except that one fair lock contains a special method called HasqueuedToraise (), also used in AQS, which essentially determines whether the precursor node of a node is the head node

# #AbstractQueuedSynchronizer
public final boolean hasQueuedPredecessors(a) {
    Node t = tail; 
    Node h = head;
    Node s;
    returnh ! = t && ((s = h.next) ==null|| s.thread ! = Thread.currentThread()); }Copy the code

The rest of the section is almost the same as the unfair locking described in the previous analysis

protected final boolean tryAcquire(int acquires) { final Thread current = Thread.currentThread(); int c = getState(); if (c == 0) { if (! Hasqueuedtoraise () && compareAndSetState(0, acquires)) {setExclusiveOwnerThread(current) set the current thread to the exclusive thread of the current lock; Return true; }} // If the current thread holds the lock information, Else if (current == getExclusiveOwnerThread()) {int nexTC = c + acquires; if (nextc < 0) throw new Error("Maximum lock count exceeded"); // Set the value of state setState(nexTC); Return true; } return false; }Copy the code

Note that tryAcquire failed only if false is returned. This leads to the tedious **addWaiter(Node.exclusive)** method

2. Failed to obtain a common lock

If tryAcquire fails, addWaiter(node.exclusive) will follow.

# #AbstractQueuedSynchronizer
private Node addWaiter(Node mode) {
    // Create a new node. Mode is Node.EXCLUSIVE = null
    Node node = new Node(Thread.currentThread(), mode);
    // Get the tail node
    Node pred = tail;
    // If the tail node is not empty, set the newly added node to the tail node and return the current node
    if(pred ! =null) {
        node.prev = pred;
        if (compareAndSetTail(pred, node)) {
            pred.next = node;
            returnnode; }}// If the tail node is empty, it indicates that the current queue is empty
    enq(node);
    return node;
}
Copy the code

3. Node joining method

The enq(node) method is the method of enq enq enq enq enQ enQ enQ enQ enQ enQ enQ enQ enQ enQ enQ enQ enQ enQ enQ enQ enQ enQ enQ enQ enQ enQ enQ enQ enQ enQ enQ enQ

# #AbstractQueuedSynchronizer
private Node enq(final Node node) {
for (;;) {
Node t = tail;
// If the last node is empty, a new node needs to be inserted as the head node
        if (t == null) {
if (compareAndSetHead(new Node()))
tail = head;
} else {
    // If not empty, change the current node to the tail node and return the precursor node of the current node
        node.prev = t;
if (compareAndSetTail(t, node)) {
t.next = node;
returnt; }}}}Copy the code

AddWaiter (Node Node) is the same as addWaiter(Node Node).

4. AcquireQueued method

The current node has been added to the blocking queue and entered the acquireQueued method. This method is also used in AQS.

# #AbstractQueuedSynchronizer
final boolean acquireQueued(final Node node, int arg) {
    boolean failed = true;
    try {
        boolean interrupted = false;
        for (;;) {
         // Get the precursor node of the current node
            final Node p = node.predecessor();
            // If the current node is preceded by a head node, the tryAcquire method is executed again
            / / lock
            if (p == head && tryAcquire(arg)) {
             // Set the current node to the head node
                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

Note the implementation details of setHead(node) : Thread is null and prev is null. If the current node’s precursor is a head node, then the current node becomes a head node, which is the virtual head node that blocked the queue.

private void setHead(Node node) {
    head = node;
    node.thread = null;
    node.prev = null;
}
Copy the code

If not for the first node or tryAcquire () method performs more cumbersome methods fail to perform the following shouldParkAfterFailedAcquire (p, node), The parkAndCheckInterrupt() method below is executed only if it returns true, both of which are methods in AQS.

# #AbstractQueuedSynchronizer
private static boolean shouldParkAfterFailedAcquire(Node pred, Node node) {
    // Get the status of the precursor node
int ws = pred.waitStatus;
// If the precursor node is in SIGNAL state, then it can sleep directly, because if a node enters
    // To block a queue, its precursor's waitStatus must be SIGNAL.
    if (ws == Node.SIGNAL)
return true;
// If the precursor Node is not in node. SIGNAL state, the Node's waitStatus must be traversed
    // The node is in SIGNAL state
    if (ws > 0) {
do {
node.prev = pred = pred.prev;
} while (pred.waitStatus > 0);
pred.next = node;
} else {
    // If no matching node is found, the current node's precursor is waitStatus
        // Set to SIGNAL
    compareAndSetWaitStatus(pred, ws, Node.SIGNAL);
}
return false;
}
Copy the code

If the value returned is false, note that the thread continues into the next dead-loop, because if it is possible that its drive node has become the head node, the lock can be acquired again. If it is not, the thread must be suspended using parkAndCheckInterrupt().

private final boolean parkAndCheckInterrupt(a) {
LockSupport.park(this);
return Thread.interrupted();
}
Copy the code

5. Cancel the request

So it all ends up in the cancelAcquire method anyway, right

private void cancelAcquire(Node node) {
if (node == null)
return;
node.thread = null;
Node pred = node.prev;
// Skip all unrequested nodes
    while (pred.waitStatus > 0)
node.prev = pred = pred.prev;
Node predNext = pred.next;
// Set the current node to cancel, skip us for subsequent traversal
    node.waitStatus = Node.CANCELLED;
// If the current node is the tail node, and the previous node was successfully set to the tail node
    if (node == tail && compareAndSetTail(node, pred)) {
    // The successor node of the current node's precursor node is empty
    compareAndSetNext(pred, predNext, null);
} else {
    int ws;
// If the precursor node is not the head node
        if(pred ! = head &&// The status of the precursor is Node.SIGNAL or the waitStatus setting of the precursor
        / / the Node SIGNAL
        ((ws = pred.waitStatus) == Node.SIGNAL ||
(ws <= 0 && compareAndSetWaitStatus(pred, ws, Node.SIGNAL))) &&
// Thread on the precursor node is not emptypred.thread ! =null) {
// Get the successor node of the current node
            Node next = node.next;
// If the successor is not empty and the successor waitStatus is less than 0
            if(next ! =null && next.waitStatus <= 0)
// Sets the successor of the current node to the successor of the current node's predecessor
            compareAndSetNext(pred, predNext, next);
} else {
// Wake up the current node if the preceding node is head or other conditions are not met
        unparkSuccessor(node);
}
node.next = node; // help GC}}Copy the code

UnparkSuccessor (node) wakes up the current node, this method is a method for AbstractQueuedSynchronizer

private void unparkSuccessor(Node node) {
    // Get the status of the current node
    int ws = node.waitStatus;
    // If the current node state is less than 0, set it to 0
    if (ws < 0)
        compareAndSetWaitStatus(node, ws, 0);
    // Get the successor node of the current node
    Node s = node.next;
    // If the successor node is empty, or the state of the successor node is less than 0
    if (s == null || s.waitStatus > 0) {
        // Set the subsequent node to null. The node that is considered to have cancelled the request
        s = null;
        // Get the last node, and the last node is not empty, not the current node, then go forward to find
        // node waitStatus The node whose state is less than 0 is assigned to the successor of the current node
        for(Node t = tail; t ! =null&& t ! = node; t = t.prev)if (t.waitStatus <= 0)
                s = t;
    }
    if(s ! =null)
     // Wake up the successor node
        LockSupport.unpark(s.thread);
}
Copy the code

3. Get the flow chart of the lock

The flow chart is very similar to the flow chart of obtaining an unfair lock in the previous article with only a few differences and there is no more description here.

4. Implementation of lock release

4.1 Analysis of lock release code

Try to release the lock. If the current thread is the holder of the lock, the retention count is reduced. If the hold count is now zero, the lock is released. If the current thread is not the holder of the lock, it throws IllegalMonitorStateException.

# #ReentrantLock
public void unlock(a) {
    sync.release(1);
}
Copy the code

Is AbstractQueuedSynchronizer sync. Release (1) call the release method

# #AbstractQueuedSynchronizer
public final boolean release(int arg) {
    if (tryRelease(arg)) {
        Node h = head;
        if(h ! =null&& h.waitStatus ! =0)
            unparkSuccessor(h);
        return true;
    }
    return false;
}
Copy the code

Analyze the tryRelease(ARG) method

TryRelease (ARG) This method is called in ReentrantLock

protected final boolean tryRelease(int releases) {
// Subtract the number of threads held by the current lock from the value to be released
    int c = getState() - releases; 
    Throw an exception if the current thread is not the thread that owns the lock
    if(Thread.currentThread() ! = getExclusiveOwnerThread())throw new IllegalMonitorStateException();
    boolean free = false;
    // If c = 0, state = 0, the current lock is not held by any thread
    // Set the current thread of ownership to null
    if (c == 0) {
        free = true;
        setExclusiveOwnerThread(null);
    }
    // Set state to 0
    setState(c);
    return free;
}
Copy the code

If the head node is not empty and waitStatus! = 0, wake up subsequent nodes if they exist. Why is the judgment condition here h! = null && h.waitStatus ! = 0?

Because if h == null, Head is not initialized. In the initial case, head == null, the first node to join the queue, head will initialize a virtual node. So head == null is the case if you haven’t entered the team yet.

  1. h ! = null && waitStatus == 0 indicates that the thread corresponding to the successor node is still running and does not need to be woken up
  2. h ! = null && waitStatus < 0 indicates that the successor node may be blocked and needs to be woken up
private void unparkSuccessor(Node node) {
// Get the header waitStatus
    int ws = node.waitStatus;
    if (ws < 0)
        compareAndSetWaitStatus(node, ws, 0);
// Get the next node of the current node
    Node s = node.next;
// If the next node is null or cancelled, the non-cancelled node at the beginning of the queue is found
    if (s == null || s.waitStatus > 0) {
        s = null;
        // Find the first node in the queue whose waitStatus<0.
        for(Node t = tail; t ! =null&& t ! = node; t = t.prev)if (t.waitStatus <= 0)
                s = t;
    }
  // If the next node of the current node is not empty and the state is <=0, the current node is awakened
    if(s ! =null)
        LockSupport.unpark(s.thread);
}
Copy the code

Why look back for the first non-cancelled node? Look at the addWaiter method

private Node addWaiter(Node mode) {
    Node node = new Node(Thread.currentThread(), mode);
    Node pred = tail;
    if(pred ! =null) {
        node.prev = pred;
        if (compareAndSetTail(pred, node)) {
            pred.next = node;
            return node;
        }
    }
    enq(node);
    return node;
}
Copy the code

Node. prev = pred, compareAndSetTail(pred, node), pred. Next = node; Not yet. If the unparksucceeded method had been carried out at this time there would have been no way of looking forward, so one had to go from the back to the front. Another reason is that when CANCELLED nodes are generated, the Next pointer is disconnected first, while the Prev pointer is not. Therefore, the whole Node must be traversed from back to front. Therefore, if the search is conducted from front to back, all nodes may not be traversed due to the non-atomic joining operation and the operation of the Next pointer being interrupted by the CANCELLED node generation process in extreme cases. So, after the corresponding thread is woken up, the corresponding thread will continue to execute.

4.2 Flowchart for Releasing locks

5. Pay attention to

Next article explains concurrent toolkit LockSupport, thank you for your attention and support! Have a problem hope everybody points out, common progress!!