AQS first experience

AQS first experience

AQS is AbstractQueuedSynchronizer short. AQS provides a framework for implementing a series of blocking locks and dependent FIFO queue of waiting for synchronization. The so-called framework, AQS used design pattern template approach, as we have blocked a series of complex operations such as internal queue, so that we focus on achieving lock-related functions.

Acquire a lock

Now it comes to the issue of competition lock, necessarily requires a flag to represent the state of the lock, AQS provides such a member state variable, in order to secure operating state, we need to use atomic operations. Modify the state from 0 to 1 on behalf of this thread already owns the lock.
But the competition will not only lock a thread, other threads are not competing to lock the how to deal with?
The first answer might be to retry , retry is good, but can not drink more, if the competition is very serious, many threads in the continuing attempt to re-acquire the lock, the CPU sooner or later we will be too much.
The second way is a better line up , after the thread holding the lock releases the lock, the next thread to acquire a lock notice to avoid unnecessary loss of CPU. But it is worth noting that, even from the queue to get in was awakened thread lock there is still not possible to obtain, because all the time there are new entrants to compete thread lock.
AQS is actually using the double-ended queue to solve this problem.

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

tryAcquire () addWaiter if the execution fails acquireQueued () in () method, that is trying to join the waiting queue. The use of a double-ended queue queues implemented, defines a data structure of a Node in the AQS, AQS maintains the head and tail of two member variables.
Inserting the tail of the queue in a single thread is very simple, just to point to the new node is inserted into the original tail next, and the tail back to the newly inserted node. But in a multithreaded environment, multiple threads is very likely to occur while inserting the tail of the phenomenon, and above the insertion process does not have the atom, while the process of inserting the confusion of multiple sequence of operations will occur, resulting in waiting queue tail node
AQS atomic operation used when inserting the tail node to ensure reliable insertion.

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 node;
        }
    }
    enq(node);
    return node;
}

Successful insertion Node return directly, without an intervening successful ENQ is executed () function used in the CAS insertion ENQ () in.

private Node enq(final Node node) {
    for (;;) {
        Node t = tail;
        if (t == null) { // Must initialize
            if (compareAndSetHead(new Node()))
                tail = head;
        } else {
            node.prev = t;
            if (compareAndSetTail(t, node)) {
                t.next = node;
                return t;
            }
        }
    }
}

CAS is inserted through this, and finally all of the nodes will be inserted into the end of the queue.

Now, do not get to lock the threads have been put in the queue, but also represents us in the queue can forget the early heart. Our goal is to acquire the lock, instead of entering the queue. acquireQueued () just trying to get a lock for us.

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

It simply is to check that he is not a head node to the next node, and if so, try to acquire to acquire locks; if not, the park will use LockSupport method blocks the current thread, to avoid wasting CPU.

Release the lock

Process releases the lock can be divided into two parts:

  1. AQS is to restore the state of lock-free state
  2. Next wakeup node waits for a waiting queue

In the first process, the team is not ranked in the head node have been blocked, and wake-up time is before a node lock has been released, it can be said that the waiting queue, in fact, is a wake-up chain.

public final boolean release(int arg) {
    if (tryRelease(arg)) {
        Node h = head;
        if (h != null && h.waitStatus != 0)
            // 使用unpark唤醒下一个线程
            unparkSuccessor(h);
        return true;
    }
    return false;
}

to sum up

AQS provides us with:

  • status state synchronization mark
  • Node deque memory lock contention thread
  • Wake-up mechanism thread-based Node deque

I think that the essence of AQS, will reduce the original N concurrent threads competing lock to 1 + M (new entrants) months. In our own algorithm to achieve a similar competition for resources, it is also possible to reduce the degree of competition by adding a concurrent queue, reducing the CPU load pressure.

Guess you like

Origin www.cnblogs.com/NinWoo/p/11243439.html