This paper mainly

Antecedents feed

In the previous article, the synchronized keyword in the JDK can achieve synchronization lock, and detailed analysis of the underlying implementation principle.

Although synchronized is no longer criticized in terms of performance, it still lacks some flexibility in practical use.

For example, in some scenarios, it is necessary to try to acquire the lock, and if the lock fails, the wait is no longer carried out, or a certain waiting time is set, and the wait is abandoned after the timeout.

At the beginning of the text, ReentrantLock is introduced

Java. Util. Concurrent. The locks package offers a variety of the realization of the locking mechanism, this paper put in already, for example, to explore how Java synchronization locks.

ReentrantLock, reentrant — reentrant, meaning that the thread holding the lock can be locked more than once.

The use cases are as follows:

ReentrantLock lock = new ReentrantLock(true);
lock.lock();
try {
    //do something
} finally {
    lock.unlock();
}
Copy the code

AQS

ReentrantLockThe underlying layer is implemented by the AQS framework, andjava.util.concurrent.locksThe various synchronization locks provided are subclasses of AQS.AQS, first look at the name,AbstractQueuedSynchronizerAbstract queue synchronizer. AQS is the cornerstone of synchronous lock implementation in Java, created by the Java godsDoug LeaA knife.

AbstractQueuedSynchronizer the core idea is to provide a synchronous queue, will not get to lock the thread blocking the line.

The key method provided is the following, where the template pattern is used, and the implementation of the method is provided by subclasses.

Public final void acquire(int arg) {} Public final void acquire(int arg) {} public final Boolean release(int arg) { boolean tryAcquire(int arg) { throw new UnsupportedOperationException(); } / / try to release the lock protected Boolean tryRelease (int arg) {throw new UnsupportedOperationException (); }Copy the code

Queues in AQS are implemented by a double-ended list of Nodes.

Static final class Node {static final Node SHARED = new Node(); Static final Node EXCLUSIVE = null; Static final int CANCELLED = 1; Static final int SIGNAL = -1; Static final int condition = -1; Static final int PROPAGATE = -3; // status, default 0 volatile int waitStatus; // Volatile Node prev; // Volatile Node next; volatile Thread thread; Node nextWaiter; }Copy the code
/** / private transient volatile Node head; // PRIVATE TRANSIENT volatile Node tail; // Synchronization status private volatile int state;Copy the code

ReetrantLock principle

1. Create a fair lock

ReentrantLock Lock = new ReentrantLock(true), the true parameter indicates that a fair lock is created.

public ReentrantLock(boolean fair) {
    sync = fair ? new FairSync() : new NonfairSync();
}
Copy the code

2. Execute lock()

final void lock() {
    acquire(1);
}
Copy the code

Acquire () is provided by AQS and cannot be overridden.

public final void acquire(int arg) { // 1. Try to get the lock if (! TryAcquire (ARG) && // 2. If not, join queue //addWaiter, create a node and set the next node to NULL, AcquireQueued (addWaiter(Node.exclusive), arg)) // Set selfInterrupt(); }Copy the code
  1. TryAccquire () is implemented by subclasses according to their own policies in an attempt to acquire the lock
  2. If the fetch fails, addWaiter() encapsulates the current thread as a Node and sets it as a tail
  3. AcquireQueued () is executed to add the current node to the synchronization queue

tryAccquire

protected final boolean tryAcquire(int acquires) { final Thread current = Thread.currentThread(); int c = getState(); If (c == 0) {hasqueued24 Returns whether to queue, and true if (! Hasqueuedtoraise () && // Set state = 1 compareAndSetState(0, acquires) { Set the lock thread to the current thread setExclusiveOwnerThread(current). return true; Else if (current == getExclusiveOwnerThread()) {int nexTC = c + acquires; if (nextc < 0) throw new Error("Maximum lock count exceeded"); setState(nextc); return true; } return false; }Copy the code

1. For tryAcquire, the synchronization state should be obtained first. If it is not 0, it means that there is a thread holding the lock, so it can determine whether it is the current thread. 2, if state = 1, check whether the need to queue hasqueued24, if the need to queue does not continue to judge, go join the queue logic. 3. If there is no need to queue, set synchronization status state = 1 through CAS. If the setting fails, the queuing will be started; if the setting succeeds, the locking thread will be set as the current thread.

addWaiter()

private Node addWaiter(Node mode) { Node node = new Node(Thread.currentThread(), mode); // Try the fast path of enq; // Set the current Node as the tail Node. Node pred = tail; if (pred ! = null) {// Set a node to be the end node node.prev = pred; If (compareAndSetTail(pred, node)) {if (compareAndSetTail(pred, node)) {next = node; return node; }} // If the tail is null, or CAS fails, spin enq() to enq(node); return node; } private Node enq(final Node Node) {// spin for (;;) { Node t = tail; If (t == null) {// Must initialize // if (t == null) {  tail = head if (compareAndSetHead(new Node())) tail = head; } else {// Set the last node of the current node to tail node.prev = t; If (compareAndSetTail(t, node)) {if (compareAndSetTail(t, node)) { return t; }}}}Copy the code

1. If the queue needs to be queued, encapsulate the thread into a Node. If the tail Node is empty, it indicates that the queue has not been initialized, and enQ (Node) is used to initialize the queue. 2. If the tail node is not empty, CAS attempts to set the current node as the tail node and returns the current node. 3. If the CAS fails to execute, use ENq (node) to spin to join the end of the queue.

acquireQueued()

final boolean acquireQueued(final Node node, int arg) { boolean failed = true; try { boolean interrupted = false; for (;;) {// p is the previous Node final Node p = node.predecessor(); If (p == head && tryAcquire(arg)) {setHead(node); p.next = null; // help GC failed = false; return interrupted; } // Check if the thread needs to block and wait, if the previous one is not waiting to wake up, spin // If the previous one is waiting to wake up, The current thread is blocked waiting for the 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) {// If (ws > 0) { /* * Predecessor was cancelled. Skip over installations and * indicate retry. */ do {node.prev = pred = pred.prev; } while (pred.waitStatus > 0); pred.next = node; CompareAndSetWaitStatus (pred, ws, Node.signal); } return false; }Copy the code
Private final Boolean parkAndCheckInterrupt() {// Block thread locksupport.park (this); return Thread.interrupted(); }Copy the code

AcquireQueued adds the current node to the queue and sets blocking. Spin, determine if the current node is the precursor node. If tryAcquire() is the head (head is not queued, only the state is queued, and the rear drive of head is really queued first), try tryAcquire() again. 2. You can see that the spin exit condition is that the current node is the first in the queue and has acquired the lock. 3, if the spin, may be consuming CPU resources, so using shouldParkAfterFailedAcquire judge whether the current thread need to be blocked, if it is blocked by parkAndCheckInterrupt thread to run. Locksupport.park () is a thread blocking implemented via the native method unsafe.park ().

Unlock ()

The logic of releasing locks is relatively simple.

public void unlock() {
    sync.release(1);
}
Copy the code

AQS release() method.

Public final Boolean release(int arg) {if (tryRelease(arg)) {Node h = head; // Queue elements need to wake up if (h! = null && h.waitStatus ! = 0) unparkSuccessor(h); return true; } return false; }Copy the code

tryRelease()

Protected final Boolean tryRelease(int releases) {// Multiple re-entries require multiple releases int c = getState() -releases; if (Thread.currentThread() ! = getExclusiveOwnerThread()) throw new IllegalMonitorStateException(); boolean free = false; If (c == 0) {free = true; setExclusiveOwnerThread(null); } // setState(c); return free; }Copy the code

unparkSuccessor()

private void unparkSuccessor(Node node) { int ws = node.waitStatus; If (ws < 0) compareAndSetWaitStatus(node, ws, 0); Node s = node.next; / / if the next node status is cancelled, the starting end node cycle, find the most if not cancelled in front of the node (s = = null | | s. aitStatus > 0) {s = null; for (Node t = tail; t ! = null && t ! = node; t = t.prev) if (t.waitStatus <= 0) s = t; } // unpark wakes up the thread if (s! = null) LockSupport.unpark(s.thread); }Copy the code

Unlock () calls AQS release() and releases the lock by tryRelease(). TryRelease () is implemented by subclasses, reentrantLock release logic is state -1, state = 0 is considered complete, multiple reentrant needs multiple release. Wake up the next waiting thread by AQS unparksucceeded (). 4. Wake up the thread with locksupport. unpark(s.read).

Fair and unfair

In practice, the difference between fair and unfair is as follows:

1, the process of fair lock is that the current thread holds the lock, then the new thread obediently queue.

2, unfair lock process is, the new line to grab a try, did not grab to queue up again.

The locking logic for an unfair lock is as follows.

final void lock() {
    if (compareAndSetState(0, 1))
        setExclusiveOwnerThread(Thread.currentThread());
    else
        acquire(1);
}
Copy the code

Summarize the core of AQS

AQS uses several core operations to control synchronous locking.

Summarize the ReentrantLock process

This is not the end, want to completely fix every step of the details, but also to look at the source, savor.

Reply to AQS for mind maps and execution flow charts.

Pay attention to, do not get lost, the old driver does not start regularly.