Exclusive resources

Access to exclusive

process
  1. If the system attempts to obtain resources successfully, the system returns directly. If the system fails, the system enters the queue and tries to obtain resources again.

  2. If successful, change the head node to its own node and remove the old head node from the queue. Failure blocks a second attempt to obtain the resource before modifying the precursor snapshot WS.

  3. If successful, the head node is changed to its own node and the old head node is removed from the queue. Failure blocks.

Release the exclusive

Process:
  1. An attempt is made to free the exclusive resource and, if successful, to wake up the descendants of the head node (if there are any to wake up). Return false on failure.

Analysis of queue state in multithreading

If thread A and thread B try to acquire the lock one after another, each thread must insert itself into the end of the queue. (Guaranteed by CAS Tail) The queue state has the following possibilities

  1. Head -> B (tail = B)
  2. Head -> A -> B (tail = B)
  3. Head -> B -> A (tail = A)

In the queue, only the successors of the head node can attempt to obtain the resource. (Of course, the head node is not guaranteed to be the latest value.) Assume that thread A is the successor thread A of the HEAD node (i.e., state 2). After thread A successfully obtains resources, the queue is as follows:

  • A->B (head = A, tail = B) If thread C comes in, if THREAD A doesn’t grab thread C. The queue status is the same as 2. If C does not grab A, the queue status is shown in the following figure
  • A->B->C (head = A, tail = C)

Analysis of running state in multithreading

  1. When thread A releases the lock, thread B fails to acquire the lock and is not in the queue, but thread B will try to acquire the resource again when it enters the queue.
  2. Thread A releases the lock while thread B enters the queue and does not acquire the resource again. Thread A releases the lock. Thread B succeeds in obtaining the resource.
  3. When thread A releases the lock, thread B enters the queue and fails to acquire the resource again, but has not modified WS to cause thread A to release the lock without waking up thread B. But thread B will try to get the resource again after WS changes.
  4. If thread A releases the lock, thread B enters the queue and fails to obtain the resource again but has not modified WS, thread B will try to obtain the resource again after WS has modified. After thread B successfully obtains the resource, thread A wakes up the successor node (that is, thread B comes out with unpark-1 state).
  5. When thread A releases the lock, thread B enters the queue to obtain resources and fails again. After modifying WS, thread A blocks and releases the lock. Thread B will wake up
  6. If thread A releases the lock, thread B enters the queue to obtain resources and fails again. After ws modification, thread A will block after releasing the lock, and thread B will still wake up. The above only considers the case of 2 threads. In the case of multiple threads, simply replace thread B with the thread corresponding to the head successor node in the queue. You can see that. When a thread releases a lock, subsequent nodes of the queue head must be able to acquire the lock.
` ` `java
    public final void acquire(int arg) {
        if(! tryAcquire(arg) && acquireQueued(addWaiter(Node.EXCLUSIVE), arg))AcquireQueued returns an interrupt flag. When this method returns, the resource was obtained successfully. Otherwise it blocks on the method
            selfInterrupt();
    }

Copy the code
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)) { // Only the head node in the queue can attempt to obtain resources. Note that head must be the latest value! The head can only be changed if the node in the queue obtains the resource, and only the successors of the head in the queue have the opportunity to obtain the resource.
                    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
private static boolean shouldParkAfterFailedAcquire(Node pred, Node node) {
        int ws = pred.waitStatus;
        if (ws == Node.SIGNAL)
            /* * This node has already set status asking a release * to signal it, so it can safely park. */
            return true;
        if (ws > 0) {
            /* * Predecessor was cancelled. Skip over predecessors and * indicate retry. */
            do {
                node.prev = pred = pred.prev;
            } while (pred.waitStatus > 0);
            pred.next = node;
        } else {
            /* * waitStatus must be 0 or PROPAGATE. Indicate that we * need a signal, but don't park yet. Caller will need to * retry to make sure it cannot acquire before parking. */
            compareAndSetWaitStatus(pred, ws, Node.SIGNAL);
        }
        return false;
    }
Copy the code

H in statement 1 is a snapshot of the head node.

If h == null, it indicates that the head in the queue is successfully obtained and the head is modified, and the old head is reclaimed by GC.

If h.ws == 0, there is no successor to wake up, where WS is guaranteed visibility.

    public final boolean release(int arg) {
        if (tryRelease(arg)) {
            Node h = head;
            if(h ! =null&& h.waitStatus ! =0) / / 1
                unparkSuccessor(h);
            return true;
        }
        return false;
    }
Copy the code
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)
            LockSupport.unpark(s.thread);
    }
Copy the code