The simplest AQS in history

Keywords: REST

Reentrant exclusive lock

What is reentry:

public void say(){
	synchronized(this){
		System.out.println("1");
		synchronized(this){ //If it's not reentrant here, the agent is stuck here, which is equivalent to deadlock
			System.out.println("2");
		}
	}
	
}

Design exclusive lock

First, you need a state equal to 0, which means that no thread has acquired the lock. If it is greater than 0, it means that a thread has acquired the lock, and an error is reported when acquiring the lock again
If you want to support reentrant, you need to save the thread obtaining the lock. Next time the thread acquires the lock again, you can use state+1.

How to deal with lock acquisition by multiple threads

If thread A1 acquires a lock, what should thread A2 do if it also acquires a lock? A2 finds that another thread has acquired the lock, so it needs to save A2, that is to say, blocking. After A1 releases the lock, A1 informs A2, and then A2 continues to acquire the lock.
If there are A3 and A4, we need to design a queue. The queue header, that is, the Head, always holds the lock and the queue is FIFO.

Exclusive lock AQS source code

 public final void acquire(int arg) {
        if (!tryAcquire(arg) &&  //Attempt to acquire lock, subclass implementation, failed to acquire. Wrap the thread as Node and put it at the end of the queue. If there is no queue, create a new queue. head holds the lock by default
            acquireQueued(addWaiter(Node.EXCLUSIVE), arg)){
                  selfInterrupt();//In the process of obtaining the lock, if it is interrupted, it is necessary to supplement the interruption
	      	}
    }


final boolean acquireQueued(final Node node, int arg) {
        boolean failed = true; //Failed to queue or not
        try {
            boolean interrupted = false; //Whether it has been interrupted
            for (;;) {
                final Node p = node.predecessor(); //The predecessor node of the current node
                if (p == head && tryAcquire(arg)) { //The precursor node is head, which means the current node can try to acquire the lock
                    setHead(node);//Get the lock, set the Head as the current node, because the current node holds the lock
                    p.next = null; // help GC
                    failed = false;
                    return interrupted;
                }
                if (shouldParkAfterFailedAcquire(p, node) &&//The current node does not acquire the lock. Set the precursor node to notify, and then block the current thread
                    parkAndCheckInterrupt()) //Have you ever been interrupted
                    interrupted = true;
            }
        } finally {
            if (failed)
                cancelAcquire(node);//Failed to acquire lock, delete current node
        }
    }

 public final boolean release(int arg) { //Release exclusive lock
        if (tryRelease(arg)) {//Subclass implementation
            Node h = head;//Head node holds lock
            if (h != null && h.waitStatus != 0)//There is a header node and the state is not initialized
                unparkSuccessor(h);//Change the state of the head node and call the thread of the successor node
            return true;
        }
        return false;
    }


Shared lock

Shared implementations have subclasses to implement, and the rest are basically the same as exclusive locks

AQS source code

 public final void acquireShared(int arg) {
        if (tryAcquireShared(arg) < 0) //Subclass implementation, get failed, enter queue, block
            doAcquireShared(arg);
    }

private void doAcquireShared(int arg) {
        final Node node = addWaiter(Node.SHARED);//Join end of team, mark as share
        boolean failed = true;
        try {
            boolean interrupted = false;
            for (;;) {
                final Node p = node.predecessor();//Precursor node
                if (p == head) { //The precursor node is the head node, so it attempts to acquire the lock
                    int r = tryAcquireShared(arg);
                    if (r >= 0) {//The total number of shared locks is required. If it is greater than or equal to 0, it means that other threads can be woken up
                        setHeadAndPropagate(node, r); 
                        p.next = null; // help GC
                        if (interrupted)
                            selfInterrupt();
                        failed = false;
                        return;
                    }
                }
                if (shouldParkAfterFailedAcquire(p, node) &&//Same as exclusive lock logic
                    parkAndCheckInterrupt())
                    interrupted = true;
            }
        } finally {
            if (failed)
                cancelAcquire(node);
        }
    }

private void setHeadAndPropagate(Node node, int propagate) {
        Node h = head; // Record old head for check below
        setHead(node);
        /*
         * Try to signal next queued node if:
         *   Propagation was indicated by caller,
         *     or was recorded (as h.waitStatus either before
         *     or after setHead) by a previous operation
         *     (note: this uses sign-check of waitStatus because
         *      PROPAGATE status may transition to SIGNAL.)
         * and
         *   The next node is waiting in shared mode,
         *     or we don't know, because it appears null
         *
         * The conservatism in both of these checks may cause
         * unnecessary wake-ups, but only when there are multiple
         * racing acquires/releases, so most need signals now or soon
         * anyway.
         */
        if (propagate > 0 || h == null || h.waitStatus < 0 ||
            (h = head) == null || h.waitStatus < 0) {
            Node s = node.next;
            if (s == null || s.isShared())
                doReleaseShared();//If the next node is shared
        }
    }

Condition

 public final void await() throws InterruptedException {
            if (Thread.interrupted())
                throw new InterruptedException();
            Node node = addConditionWaiter(); //Queue the current thread
            int savedState = fullyRelease(node); //Release the lock held by the current thread
            int interruptMode = 0;
            while (!isOnSyncQueue(node)) { //It's useless here. Only when two threads are concurrent can TRUE be returned
                LockSupport.park(this);//block
                if ((interruptMode = checkInterruptWhileWaiting(node)) != 0)
                    break;
            }
            if (acquireQueued(node, savedState) && interruptMode != THROW_IE)
                interruptMode = REINTERRUPT;
            if (node.nextWaiter != null) // clean up if cancelled
                unlinkCancelledWaiters();
            if (interruptMode != 0)
                reportInterruptAfterWait(interruptMode);
        }


Posted by Fabio9999 on Sat, 21 Mar 2020 09:29:28 -0700