This article takes you to learn the relationship between AQS and concurrency tools 2

1. Create a fair lock

1. How to use

Lock reentrantLock = new ReentrantLock(true);
reentrantLock.lock(); //加锁
try{
    
    
  // todo
} finally{
    
    
  reentrantLock.unlock(); // 释放锁
}

2. Create a fair lock

Add the keyword true when new ReentrantLock(true)

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

When the passed parameter value is true, the object created is **new FairSync()** fair lock.

2. The realization of locking

1. Ordinary acquisition lock

reentrantLock.lock(); //加锁

The actual method of locking is the lock method in the fair lock created
image

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

The acquire method in the code is the same as the acquire method in the unfair lock, which is the final method in the called AQS

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

But the difference is that the tryAcquire(arg) method is the method implemented in the fair lock called
image

This method is actually very similar to the unfair lock method. There is a special method called hasQueuedPredecessors() in a different fair lock. This method is also a method in AQS. The essence of this method is to determine whether the predecessor node of the node is head. node

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

The remaining part and the unfair lock analyzed in the previous article are almost a process

protected final boolean tryAcquire(int acquires) {
    final Thread current = Thread.currentThread();
    int c = getState();
    if (c == 0) {
        if (!hasQueuedPredecessors() &&
            compareAndSetState(0, acquires)) {
            // 设置当前线程为当前锁的独占线程
            setExclusiveOwnerThread(current);
            // 获取锁成功
            return true;
        }
    }
    // 如果是当前线程持有的锁信息,在原来的state的值上加上acquires的值
    else if (current == getExclusiveOwnerThread()) {
        int nextc = c + acquires;
        if (nextc < 0)
            throw new Error("Maximum lock count exceeded");
        // 设置state的值
        setState(nextc);
        // 获取锁成功
        return true;
    }
    // 获取锁失败了才返回false
    return false;
}

Note that only when it returns false is tryAcquire failed. At this point, you will come to the cumbersome **addWaiter(Node.EXCLUSIVE)** method

2. Ordinary lock acquisition failed

If the previous tryAcquire fails, the next addWaiter(Node.EXCLUSIVE) will be performed

## AbstractQueuedSynchronizer
private Node addWaiter(Node mode) {
    
    
    // 创建一个新的node节点 mode 为Node.EXCLUSIVE = null
    Node node = new Node(Thread.currentThread(), mode);
    // 获取尾部节点
    Node pred = tail;
    // 如果尾部节点不为空的话将新加入的节点设置成尾节点并返回当前node节点
    if (pred != null) {
    
    
        node.prev = pred;
        if (compareAndSetTail(pred, node)) {
    
    
            pred.next = node;
            return node;
        }
    }
    // 如果尾部节点为空整明当前队列是空值得需要将当前节点入队的时候先初始化队列
    enq(node);
    return node;
}

3. Node enlisting method

The enq(node) method is the method of node enqueue. Let's analyze it. The enq enqueue method is also the method in AQS. Pay attention to the infinite loop of this method . In any case, the node must be added to the queue.

## AbstractQueuedSynchronizer
private Node enq(final Node node) {
    
    
for (;;) {
    
    
Node t = tail;
// 如果尾节点为空的话,那么需要插入一个新的节点当头节点
        if (t == null) {
    
    
if (compareAndSetHead(new Node()))
tail = head;
} else {
    
    
    // 如果不为空的话,将当前节点变为尾节点并返回当前节点的前驱节点
        node.prev = t;
if (compareAndSetTail(t, node)) {
    
    
t.next = node;
return t;
}
}
}
}

In fact, it is the same process as the addWaiter (Node node) of the unfair lock, after the analysis.

4.acquireQueued method

At this point, the current node has been added to the blocking queue and entered the acquireQueued method. This method is also the method in AQS.

## AbstractQueuedSynchronizer
final boolean acquireQueued(final Node node, int arg) {
    
    
    boolean failed = true;
    try {
    
    
        boolean interrupted = false;
        for (;;) {
    
    
         // 获取当前节点的前驱节点
            final Node p = node.predecessor();
            // 如果当前节点的前驱节点是头节点的话会再一次执行tryAcquire方法获
            // 取锁
            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);
    }
}

Note that the specific implementation details thread in setHead(node) is null and prev is also null. In fact, if the predecessor node of the current node is the head node, then the current node becomes the head node, which is the virtual head node of the previously blocked queue.

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

If it is not the head node or the tryAcquire() method fails to execute the following more cumbersome method shouldParkAfterFailedAcquire(p, node), if the method returns true, it will execute to the following parkAndCheckInterrupt() method , both of which are in AQS method.

## AbstractQueuedSynchronizer
private static boolean shouldParkAfterFailedAcquire(Node pred, Node node) {
    
    
    // 获取前驱节点的状态
int ws = pred.waitStatus;
// 如果前驱节点的状态为SIGNAL那么直接就可以沉睡了,因为如果一个节点要是进入
    // 阻塞队列的话,那么他的前驱节点的waitStatus必须是SIGNAL状态。
    if (ws == Node.SIGNAL)
return true;
// 如果前驱节点不是Node.SIGNAL状态就往前遍历一值寻找节点的waitStatus必须
    // 是SIGNAL状态的节点
    if (ws > 0) {
    
    
do {
    
    
node.prev = pred = pred.prev;
} while (pred.waitStatus > 0);
pred.next = node;
} else {
    
    
    // 如果没有找到符合条件的节点,那么就将当前节点的前驱节点的waitStatus
        // 设置成SIGNAL状态
    compareAndSetWaitStatus(pred, ws, Node.SIGNAL);
}
return false;
}

If the returned value is false, you must pay attention to entering the next infinite loop at this time, because if it is possible that his precursor node becomes the head node in the process of traversing forward, then the lock can be acquired again, If not, then you can only
execute the parkAndCheckInterrupt() method to suspend the thread.

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

5. Cancel the request

In any case, I finally came to the cancelAcquire method

private void cancelAcquire(Node node) {
    
    
if (node == null)
return;
node.thread = null;
Node pred = node.prev;
// 跳过所有取消请求的节点
    while (pred.waitStatus > 0)
node.prev = pred = pred.prev;
Node predNext = pred.next;
// 将当前节点设置成取消状态,为了后续遍历跳过我们
    node.waitStatus = Node.CANCELLED;
// 如果当前节点是尾节点,并且将当前节点的前驱节点设置成尾节点成功
    if (node == tail && compareAndSetTail(node, pred)) {
    
    
    // 当前节点的前驱节点的后续节点为空
    compareAndSetNext(pred, predNext, null);
} else {
    
    
    int ws;
// 如果前驱节点不是头节点
        if (pred != head &&
        // 前驱节点的状态是Node.SIGNAL或者前驱节点的waitStatus设置           
        // 成Node.SIGNAL
        ((ws = pred.waitStatus) == Node.SIGNAL ||
(ws <= 0 && compareAndSetWaitStatus(pred, ws, Node.SIGNAL))) &&
// 前驱节点的thread 不为空
            pred.thread != null) {
    
    
// 获取当前节点的后继节点
            Node next = node.next;
// 如果后继节点不为空,并且后继节点waitStatus 小于0
            if (next != null && next.waitStatus <= 0)
// 将当前节点的后继节点设置成当前节点的前驱节点的后继节点
            compareAndSetNext(pred, predNext, next);
} else {
    
    
// 如果上面当前节点的前驱节点是head或者其他条件不满足那么就唤醒当前节点
        unparkSuccessor(node);
}
node.next = node; // help GC
}
}

unparkSuccessor(node) wakes up the current node, this method is also a method in AbstractQueuedSynchronizer

private void unparkSuccessor(Node node) {
    
    
    // 获取当前节点的状态
    int ws = node.waitStatus;
    // 如果当前节点状态小于0那么设置成0
    if (ws < 0)
        compareAndSetWaitStatus(node, ws, 0);
    // 获取当前节点的后继节点
    Node s = node.next;
    // 如果后继节点为空,或者后继节点的状态小于0
    if (s == null || s.waitStatus > 0) {
    
    
        // 后继节点置为null。视为取消请求的节点
        s = null;
        // 获取尾节点,并且尾节点不为空,不是当前节点,那么就往前遍历寻找
        // 节点waitStatus 状态小于0的节点赋予给当前节点的后继节点
        for (Node t = tail; t != null && t != node; t = t.prev)
            if (t.waitStatus <= 0)
                s = t;
    }
    if (s != null)
     // 唤醒后继节点
        LockSupport.unpark(s.thread);
}

3. Flow chart of acquiring lock

The flow chart is very similar to the previous flow chart for obtaining unfair locks, with only a little difference. I will not describe too much here.

4. Implementation of release lock

4.1 Analysis of release lock code

Try to release this lock. If the current thread is the holder of this lock , the reserve count will decrease. If the hold count is now zero, the lock is released. If the current thread is not the holder of this lock, an IllegalMonitorStateException is thrown.

## ReentrantLock
public void unlock() {
    
    
    sync.release(1);
}

sync.release(1) calls the release method in AbstractQueuedSynchronizer

## 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;
}

Analyze the tryRelease(arg) method
image

tryRelease(arg) This method is called in ReentrantLock

protected final boolean tryRelease(int releases) {
    
    
// 获取当前锁持有的线程数量和需要释放的值进行相减
    int c = getState() - releases; 
    // 如果当前线程不是锁占有的线程抛出异常
    if (Thread.currentThread() != getExclusiveOwnerThread())
        throw new IllegalMonitorStateException();
    boolean free = false;
    // 如果此时c = 0就意味着state = 0,当前锁没有被任意线程占有
    // 将当前所的占有线程设置为空
    if (c == 0) {
    
    
        free = true;
        setExclusiveOwnerThread(null);
    }
    // 设置state的值为 0
    setState(c);
    return free;
}

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

Because h == null, Head has not been initialized yet. In the initial situation, head == null, the first node is enqueued, and Head will be initialized as a virtual node. Therefore, if there is no time to join the team, head == null will occur.

  1. h != null && waitStatus == 0 indicates that the thread corresponding to the successor node is still running and does not need to be awakened
  2. h != null && waitStatus <0 indicates that the successor node may be blocked and needs to be awakened
private void unparkSuccessor(Node node) {
    
    
// 获取头结点waitStatus
    int ws = node.waitStatus;
    if (ws < 0)
        compareAndSetWaitStatus(node, ws, 0);
// 获取当前节点的下一个节点
    Node s = node.next;
//如果下个节点是null或者下个节点被cancelled,就找到队列最开始的非cancelled的节点
    if (s == null || s.waitStatus > 0) {
    
    
        s = null;
        // 就从尾部节点开始找往前遍历,找到队列中第一个waitStatus<0的节点。
        for (Node t = tail; t != null && t != node; t = t.prev)
            if (t.waitStatus <= 0)
                s = t;
    }
  // 如果当前节点的下个节点不为空,而且状态<=0,就把当前节点唤醒
    if (s != null)
        LockSupport.unpark(s.thread);
}

Why do you want to find the first non-Cancelled node from back to front?
Take a 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;
}

We can see from this that the entry of a node into the team is not an atomic operation, that is to say, node.prev = pred, compareAndSetTail(pred, node) these two places can be regarded as the atomic operation of Tail enrolling, but at this time pred. next = node; has not been executed yet. If the unparkSuccessor method is executed at this time, there is no way to search from the front to the back, so you need to search from the back to the front. There is another reason. When the CANCELLED state node is generated, the Next pointer is disconnected first, and the Prev pointer is not disconnected. Therefore, it is necessary to traverse from back to front to traverse all Nodes .
Therefore, if the search is from the front to the back, due to the non-atomic operation of enqueue and the operation of disconnecting the Next pointer during the generation of the CANCELLED node in extreme cases, it may be impossible to traverse all nodes . Therefore, after waking up the corresponding thread, the corresponding thread will continue to execute.

4.2 Release lock flowchart

image

5. Attention

The next article will explain LockSupport under the concurrency toolkit, thank you for your attention and support! There are problems, I hope everyone will point out and make progress together!!!

Guess you like

Origin blog.csdn.net/weixin_38071259/article/details/112789424