ReentrantLock, which means that the lock can be repeatedly locked by a thread on a resource. There are two ways to obtain the lock:

  1. Fair lock: Locks are acquired in the order in which they were requested
  2. Unfair lock: Any thread has a chance to acquire the lock when it requests it

Contrast Synchronized with ReentrantLock

Synchronized keyword modification methods and synchronized code blocks also support reentrant, the difference being:

Synchronized ReentrantLock
Lock mode Implicit lock Explicitly lock, calllock()Lock andunlock()unlock
Waiting for the lockInterruptible or not Do not interrupt interruptible
The lockfairness Not fair lock The default value is unfair lock and can be set to fair lock
The lock binds multiple conditions Only one lock condition can be bound Multiple lock conditions can be setConditionobject

ReentrantLock implements reentrant analysis

TryAcquire () gets the lock source code

final boolean nonfairTryAcquire(int acquires) {
    // Get the current thread
    final Thread current = Thread.currentThread();
    // Get the current synchronization status
    int c = getState();
    // If 0 is not locked
    if (c == 0) {
        // Try to get the lock directly
        if (compareAndSetState(0, acquires)) {
            setExclusiveOwnerThread(current);
            return true; }}// Determine whether the current thread has acquired the lock, if it is reentrant
    else if (current == getExclusiveOwnerThread()) {
        // Implement reentrant, synchronize state + 1
        int nextc = c + acquires;
        if (nextc < 0) // overflow
            throw new Error("Maximum lock count exceeded");
        setState(nextc);
        return true;
    }
    return false;
}
Copy the code

TryRelease () releases lock source code

protected final boolean tryRelease(int releases) {
    // State -1 will be synchronized
    int c = getState() - releases;
    // Determine whether the current thread is the same as the thread that has acquired the lock
    if(Thread.currentThread() ! = getExclusiveOwnerThread())throw new IllegalMonitorStateException();
    boolean free = false;
    // Determine whether the synchronization status is equal to 0. If so, release the lock
    if (c == 0) {
        free = true;
        setExclusiveOwnerThread(null);
    }
    setState(c);
    return free;
}

Copy the code

Reentrant analysis summary

  1. ReentrantLock internal maintenancestateMember variable, can achieve multiple pairsstateVariable increment implements reentrant locking
  2. To obtainnThe secondary lock will be releasednSecondary lock, only whenstateIf the value is 0, the lock is released successfully.

Fair lock and unfair lock implementation analysis

ReentrantLock has two internal classes, FairSync and NonfairSync, respectively representing the classes of fair and unfair locks. The inner class overwrites the tryAcquire() method of AQS to define the lock request mode. Let’s start with the lock() method

Lock () method analysis

// NonfairSync
final void lock(a) {
    if (compareAndSetState(0.1))
        setExclusiveOwnerThread(Thread.currentThread());
    else
        acquire(1);
}

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

The following differences can be obtained from the above code:

  1. Non – fair lock directlyCASAttempts to acquire the lock, called only when obtaining it failsacquire(), while fair lock callsacquire();

Calling acquire() actually calls the tryAcquire() methods inside FairSync and NonfairSync,

public final void acquire(int arg) {
    if(! tryAcquire(arg) && acquireQueued(addWaiter(Node.EXCLUSIVE), arg)) selfInterrupt(); }Copy the code

TryAcquire () source code parsing

NonfairSync’s tryAcquire() method

protected final boolean tryAcquire(int acquires) {
    return nonfairTryAcquire(acquires);
}

final boolean nonfairTryAcquire(int acquires) {
    // Get the current thread
    final Thread current = Thread.currentThread();
    // Get the current synchronization status
    int c = getState();
    // If 0 is not locked
    if (c == 0) {
        // Try to get the lock directly
        if (compareAndSetState(0, acquires)) {
            setExclusiveOwnerThread(current);
            return true; }}// Determine whether the current thread has acquired the lock
    else if (current == getExclusiveOwnerThread()) {
        // Implement reentrant, synchronize state + 1
        int nextc = c + acquires;
        if (nextc < 0) // overflow
            throw new Error("Maximum lock count exceeded");
        setState(nextc);
        return true;
    }
    return false;
}
Copy the code

Non-fair lock internal call nonfairTryAcquire() method to achieve non-fair lock request, source code as above; Unfair lock The whole process of obtaining locks is shown as follows:

Note that threads are woken up when all threads are woken up, rather than only threads with a head node as the precursor node, which is an important difference from fair locking

FairSync’s tryAcquire() method

protected final boolean tryAcquire(int acquires) {
    // Get the current thread
    final Thread current = Thread.currentThread();
    // Get the current synchronization status
    int c = getState();
    // Determine whether the state is reachable
    if (c == 0) {
        // If the queue is empty and the synchronization status is set successfully, the lock is obtained successfully. Otherwise, queue is required
        if(! hasQueuedPredecessors() && compareAndSetState(0, acquires)) {
            setExclusiveOwnerThread(current);
            return true; }}// Determine if the current thread has acquired the lock
    else if (current == getExclusiveOwnerThread()) {
        // If so, increment state by 1 for reentrant
        int nextc = c + acquires;
        if (nextc < 0)
            throw new Error("Maximum lock count exceeded");
        setState(nextc);
        return true;
    }
    return false;
}
Copy the code

The differences between the two lock acquisition methods:

  1. If the lock has been released (state = 0), the fair lock calls the method firsthasQueuedPredecessors()Determine whether there is a node in the queue, if so, queue, but not fair lock directlyCASTry to obtain the lock, source code as follows:
// NonfairSync's tryAcquire() method
if (c == 0) {
    // Try to get the lock directly
    if (compareAndSetState(0, acquires)) {
        setExclusiveOwnerThread(current);
        return true; }}// FairSync's tryAcquire() method
if (c == 0) {
    // If the queue is empty and the synchronization status is set successfully, the lock is obtained successfully. Otherwise, queue is required
    if(! hasQueuedPredecessors() && compareAndSetState(0, acquires)) {
        setExclusiveOwnerThread(current);
        return true; }}Copy the code

Action after a lock acquisition failure

  1. If the lock cannot be obtained, it is calledacquireQueued()Add the thread node to the end of the queue and wait for the executing thread to wake up,addWaiter()Create a waiting thread node, source code as follows:
public final void acquire(int arg) {
    if(! tryAcquire(arg) && acquireQueued(addWaiter(Node.EXCLUSIVE), arg)) selfInterrupt(); }/ / addWaiter () the source code
private Node addWaiter(Node mode) {
    // Create new nodes
    Node node = new Node(Thread.currentThread(), mode);
    Node pred = tail;
    // If the tail node is not empty, the queue is not empty
    if(pred ! =null) {
        node.prev = pred;
        if (compareAndSetTail(pred, node)) {
            pred.next = node;
            returnnode; }}// Initialize the queue
    enq(node);
    return node;
}
/ / enq () the source code
private Node enq(final Node node) {
    // Initialize the queue in an infinite loop
    for (;;) {
        Node t = tail;
        if (t == null) { // Check whether the queue is empty twice, in case the queue has already been initialized during the call
            if (compareAndSetHead(new Node()))
                tail = head;
        } else {
           	// The queue is not empty and is added to the end of the queue normally
            node.prev = t;
            if (compareAndSetTail(t, node)) {
                t.next = node;
                returnt; }}}}Copy the code

Already concluded

  1. ReentrantLock has two internal implementations, fair and unfair
  2. A fair lock acquires the lock in the order requested. An unfair lock acquires the lock simultaneously
  3. Non-fair locks perform better than fair locks because request order causes frequent thread context switches, whereas non-fair locks do not care about queue order
  4. Reentrancy is throughstateThe synchronization state increment is realized

Shoulders of giants:

The Art of Concurrent Programming in Java