Small knowledge, big challenge! This article is participating in the creation activity of “Essential Tips for Programmers”.

This article has participated in the “Digitalstar Project” and won a creative gift package to challenge the creative incentive money.

Conditional lock refers to a lock that is used when the current service scenario cannot be processed by the user after obtaining the lock and the user needs to wait for a certain condition to continue processing.

The await() method in Condition is equivalent to the wait() method of Object, and the signal() method in Condition is equivalent to the notify() method of Object, SignalAll () in Condition is equivalent to notifyAll() on Object. The difference is that these methods in Object are bundled with synchronization locks; Condition is bundled with a mutex/shared lock.

Let’s take a look at the basic use of conditional locking

public class ConditionDemo { public static void main(String[] args) throws InterruptedException { ReentrantLock reentrantLock = new ReentrantLock(); Condition condition1 = reentrantLock.newCondition(); new Thread(new Runnable() { @Override public void run() { reentrantLock.lock(); // 1 try { condition1.await(); // 2} catch (Exception e) {} finally {system.out.println (" release "); // 6 reentrantLock.unlock(); } } }).start(); Thread.sleep(1000); reentrantLock.lock(); // 3 try {system.out.println (" I want to release you "); // 4 condition1.signal(); // 5 } catch (Exception e) { } finally { reentrantLock.unlock(); // 7}}} output: I release you releaseCopy the code

The code above is very simple, one thread waits for the condition, the other notifies the condition is true, and the numbers behind represent the order in which the code actually runs. If you can understand the order in which the basic condition lock is used, you’ll have a pretty good idea.

The main properties

The conditional lock maintains a conditional queue, firstWaiter points to the conditional queue head node,

LastWaiter points to the end of the conditional queue.

Creating a Conditional lock

// ReentrantLock.newCondition()
  public Condition newCondition() {
        return sync.newCondition();
    }
   // ReentrantLock.Sync.newCondition()
        final ConditionObject newCondition() {
            return new ConditionObject();
        }

// AQS.ConditionObject
    public class ConditionObject implements Condition, java.io.Serializable {}

Copy the code

ConditionObject (ConditionObject) ConditionObject (ConditionObject) ConditionObject (ConditionObject) ConditionObject (ConditionObject)

Await method

public final void await() throws InterruptedException { if (Thread.interrupted()) throw new InterruptedException(); // add the current thread to the conditional queue. Node Node = addConditionWaiter(); Int savedState = fullyRelease(node); savedState = fullyRelease(node); The condition queue is suspended. The condition queue is suspended. The condition queue is suspended. The condition queue is suspended. Int interruptMode = 0; int interruptMode = 0; // TRUE:isOnSyncQueue :isOnSyncQueue :isOnSyncQueue :isOnSyncQueue :isOnSyncQueue: while (! IsOnSyncQueue (node)) {// Suspend the thread corresponding to the current node locksupport.park (this); // When will you wake up? // 1. After the external thread has acquired the lock, it calls signal(), which moves the head node of the conditional queue to the blocking queue. // If the thread is interrupted during the condition queue suspension, the corresponding node will be moved to the "blocking queue". if ((interruptMode = checkInterruptWhileWaiting(node)) ! = 0) break; If (acquireQueued(Node, savedState) && interruptMode! = THROW_IE) interruptMode = REINTERRUPT; // Clear the cancelled node if (node.nextwaiter! = null) // clean up if cancelled unlinkCancelledWaiters(); If (interruptMode!) if (interruptMode! = 0) reportInterruptAfterWait(interruptMode); } private Node addwaiter () {Node t = lastWaiter; // Clear all cancelled nodes from the beginning if (t! = null && t.waitStatus ! = Node.CONDITION) { unlinkCancelledWaiters(); // retrieve the last node t = lastWaiter; CONDITION Node Node = new Node(thread.currentThread (), node.condition); If (t == null) firstWaiter = node; if (t == null) firstWaiter = node; else t.nextWaiter = node; // the last node points to the new node lastWaiter = node; // Return node; } final int fullyRelease(Node node) { boolean failed = true; try { int savedState = getState(); If (release(savedState)) {failed = false; return savedState; } else { throw new IllegalMonitorStateException(); } } finally { if (failed) node.waitStatus = Node.CANCELLED; } } final boolean isOnSyncQueue(Node node) { if (node.waitStatus == Node.CONDITION || node.prev == null) return false; if (node.next ! = null) // If has successor, it must be on queue return true; WaitStatus ==0 ** findNodeFromTail: findNodeFromTail(Node); findStatus ==0 ** findNodeFromTail(Node);  }Copy the code
  1. First join the conditional queue

    Joining a conditional queue adds the bottom node to the queue and sets its WaitStatus status to condition

  2. Full release lock

    So you can see that this is fullyRelease calling, not release calling, why, if you think about it, if there’s a reentrant, then the current state is not 1, so fullyRelease is just to completely release, to make sure that the lock held by that thread is completely released, And wakes up the next thread.

  3. Determines whether the current thread is in a conditional queue or a blocking queue

  4. If it is in a conditional queue, park() and wait to wake up.

  5. If the node is now blocking, the current node has been migrated to the blocking queue, and the acquireQueued method described in the previous article is spun to acquire the lock.

Signal method

Signal () public final void signal() {// If (! isHeldExclusively()) throw new IllegalMonitorStateException(); // first = firstWaiter; If (first! = null) doSignal(first); } // ReentrantLock.isHeldExclusively() protected final boolean isHeldExclusively() { // While we must in general read state before owner, // we don't need to do so to check if current thread is owner return getExclusiveOwnerThread() == Thread.currentThread(); } // AQS. doSignal() private void doSignal(Node first) { do { if ((firstWaiter = first.nextWaiter) == null) lastWaiter =  null; First. nextWaiter = null; // transferForSignal true: indicates that the current first node is successfully migrated to the blocking queue // first = firstWaiter)! = null: current first migration failed, update first to first. Next continue to try to migrate} while (! transferForSignal(first) && (first = firstWaiter) ! = null); } // AQS. TransferForSignal () final Boolean transferForSignal(Node Node) {/** * Successful: The current Node is in the conditional queue. */ if (! compareAndSetWaitStatus(node, Node.CONDITION, 0)) return false; P = enq(node); p = enq(node); Int ws = p.waitStatus; / / as long as the precursor node is not 0 or 1, then, will wake up the current thread if (ws > 0 | |! compareAndSetWaitStatus(p, ws, Node.SIGNAL)) LockSupport.unpark(node.thread); return true; }Copy the code
  1. Start from the head node of the conditional queue to find a non-cancelled node;

  2. Move it from the conditional queue to the AQS queue;

  3. And only one node is moved;

Method signalAll

public final void signalAll() { if (! isHeldExclusively()) throw new IllegalMonitorStateException(); Node first = firstWaiter; if (first ! = null) doSignalAll(first); } private void doSignalAll(Node first) { lastWaiter = firstWaiter = null; do { Node next = first.nextWaiter; first.nextWaiter = null; transferForSignal(first); first = next; } while (first ! = null); }Copy the code

The signalAll method wakes up all nodes in the conditional queue and adds them to the blocking queue one by one.

conclusion

  1. A conditional lock is a lock used to wait for a condition to occur.

  2. The classic use of conditional locks is when the queue is empty and blocks on the condition notEmpty;

  3. Conditional locks in ReentrantLock are implemented by the AQS ConditionObject inner class.

  4. Both await() and signal() methods must be used before releasing the lock after acquiring it;

  5. The await() method will create a new node and put it on the condition queue, then release the lock completely, and then block the current thread and wait for the condition to occur;

  6. The signal() method looks for the first available node in the conditional queue to move to the AQS queue;

  7. Calling unlock() in the thread that calls signal() actually wakes up the node blocking the condition (which is already in the AQS queue);

  8. The node then attempts to acquire the lock again, and the logic is basically the same as lock().