I talked about AQS before. Source code of exclusive lock , here, let's talk about the implementation of another lock, shared lock, to CountDownLatch For example.
Source code analysis
Construction method
The Sync method is an internal method, just like ReentrantLock before. The constructor needs to pass in an integer no less than 0 to assign to state. When other threads call the countDown method, the value of state is reduced by one. When it is reduced to 0, other calls to the await method will be awakened. The await method can be called by multiple threads. When it is called, it enters the blocking state until the state is 0. Later, we will focus on countDown and await methods.
public CountDownLatch(int count) { if (count < 0) throw new IllegalArgumentException("count < 0"); this.sync = new Sync(count); } Sync(int count) { setState(count); }
await
Let's first look at the blocking method. At this time, we need to wait for the state to be 0 before being awakened.
public void await() throws InterruptedException { sync.acquireSharedInterruptibly(1); } public final void acquireSharedInterruptibly(int arg) throws InterruptedException { if (Thread.interrupted())//Interrupt condition, throw exception throw new InterruptedException(); if (tryAcquireShared(arg) < 0) doAcquireSharedInterruptibly(arg);//When state is not 0 } protected int tryAcquireShared(int acquires) { return (getState() == 0) ? 1 : -1;//Current status is 0, return 1 } private void doAcquireSharedInterruptibly(int arg)//Acquire the shared lock and can be interrupted throws InterruptedException { final Node node = addWaiter(Node.SHARED);//This is different from the previous Node.SHARED. It is added to the queue and is not explained. boolean failed = true; try { for (;;) {//spin final Node p = node.predecessor();//Get front node if (p == head) { int r = tryAcquireShared(arg);//Attempt to acquire lock if (r >= 0) {//When state is 0 setHeadAndPropagate(node, r);//This method is described below p.next = null; // help GC failed = false; return; } } if (shouldParkAfterFailedAcquire(p, node) && parkAndCheckInterrupt())//This part is the same as before, hang throw new InterruptedException(); } } finally { if (failed) cancelAcquire(node); } }
countDown
public void countDown() { sync.releaseShared(1); } public final boolean releaseShared(int arg) { if (tryReleaseShared(arg)) { doReleaseShared();//true, wake up return true; } return false; } //Using spin to make status-1 protected boolean tryReleaseShared(int releases) { // Decrement count; signal when transition to zero for (;;) { int c = getState(); if (c == 0) return false; int nextc = c-1; if (compareAndSetState(c, nextc)) return nextc == 0; } } private void doReleaseShared() { for (;;) { Node h = head; if (h != null && h != tail) {//There is a blocking queue and the head node is not the tail node int ws = h.waitStatus; if (ws == Node.SIGNAL) { if (!compareAndSetWaitStatus(h, Node.SIGNAL, 0))//Set the waitStatus of the header node to 0 through cas continue; // loop to recheck cases failed to reset unparkSuccessor(h);//Wake up next node, previous content } else if (ws == 0 && !compareAndSetWaitStatus(h, 0, Node.PROPAGATE)) continue; // loop on failed CAS } if (h == head) // loop if head changed break;// } }
When just wait ing, if the lock is not acquired in doacquireshared interruptible, it will be suspended. Now that the status is 0, wake him up and continue spinning. See the following code for the number of times
private void setHeadAndPropagate(Node node, int propagate) { Node h = head; // Record old head for check below setHead(node);//Current node set as head node if (propagate > 0 || h == null || h.waitStatus < 0 || (h = head) == null || h.waitStatus < 0) { Node s = node.next;//Get next node if (s == null || s.isShared()) doReleaseShared();//Wake up next node } }