ReentrantLock for source code analysis

ReentrantLockclass belongs java.util.concurrentto. It implements Lock, java.io.Serializabletwo interfaces and is a reentrant mutex. The so-called reentrancy is that a thread can repeatedly acquire the lock it already holds.

/**
    * Creates an instance of {@code ReentrantLock}.
    * This is equivalent to using {@code ReentrantLock(false)}.
    */
   public ReentrantLock() {
       sync = new NonfairSync();
   }
   /**
    * Creates an instance of {@code ReentrantLock} with the
    * given fairness policy.
    *
    * @param fair {@code true} if this lock should use a fair ordering policy
    */
   public ReentrantLock(boolean fair) {
       sync = fair ? new FairSync() : new NonfairSync();
   }
abstract static class Sync extends AbstractQueuedSynchronizer

ReentrantLockThe mechanism for implementing locks is through Syncoperations. SyncClasses inherit from AbstractQueuedSynchronizerclasses. This also shows that it ReentrantLockis based on AQSthe implementation. '

Sync, and both are static inner classes. and are specific implementation classes, which correspond to fair locks and unfair locks respectively. Fairness mainly refers to acquiring locks in the order of principles, and unfairness can choose to acquire locks according to the defined rules.FairSyncNonFairSyncReentrantLockFairSyncNonFairSyncSyncFIFO

NonFairSync source code analysis

Unfair locking NonFairSyncis the ReentrantLockdefault implementation. Here you can take a look at its lockimplementation process:

/**
 * Performs lock.  Try immediate barge, backing up to normal
 * acquire on failure.
 */
final void lock() {
    if (compareAndSetState(0, 1))
        setExclusiveOwnerThread(Thread.currentThread());
    else
        acquire(1);
}
  • First, by CASupdating statethe state, if the update is successful, the lock is acquired, and the current thread is set as the owner of the lock.
protected final boolean compareAndSetState(int expect, int update) {
    // See below for intrinsics setup to support this
    return unsafe.compareAndSwapInt(this, stateOffset, expect, update);
}
  • If the update fails, it indicates that the current lock is held by another thread. method will be called acquire(1). acquireThe specific implementation is as follows:
public final void acquire(int arg) {
    if (!tryAcquire(arg) &&
        acquireQueued(addWaiter(Node.EXCLUSIVE), arg))
        selfInterrupt();
}
protected final boolean tryAcquire(int acquires) {
        return nonfairTryAcquire(acquires);
    }

tryAcquireprocess, will try to acquire the lock again, which is overridden tryAcquirein the static inner class class, the concrete implementation is the method:NonfairSyncSyncnonfairTryAcquire

final boolean nonfairTryAcquire(int acquires) {
    final Thread current = Thread.currentThread();
    int c = getState();
    if (c == 0) {
        if (compareAndSetState(0, acquires)) {
            setExclusiveOwnerThread(current);
            return true;
        }
    }
    else if (current == getExclusiveOwnerThread()) {
        int nextc = c + acquires;
        if (nextc < 0) // overflow
            throw new Error("Maximum lock count exceeded");
        setState(nextc);
        return true;
    }
    return false;
}

Its main process is to get statethe value first, and if it is equal to 0, then pass the CASupdated statevalue. If it stateis not 0, it is judged whether the current thread is the holder of the lock, if it is, it will stateadd 1 and return true.

If it tryAcquirestill fails, it will be called first to addWaiter(Node.EXCLUSIVE)add the current thread to the tail of the waiting queue. Then the method will be called acquireQueued, acquireQueuedwhich is mainly used to block the thread:

/**
 * Acquires in exclusive uninterruptible mode for thread already in
 * queue. Used by condition wait methods as well as acquire.
 *
 * @param node the node
 * @param arg the acquire argument
 * @return {@code true} if interrupted while waiting
 */
final boolean acquireQueued(final Node node, int arg) {
    boolean failed = true;
    try {
        boolean interrupted = false;
        for (;;) {
            final Node p = node.predecessor();
            if (p == head && tryAcquire(arg)) {
                setHead(node);
                p.next = null; // help GC
                failed = false;
                return interrupted;
            }
            if (shouldParkAfterFailedAcquire(p, node) &&
                parkAndCheckInterrupt())
                interrupted = true;
        }
    } finally {
        if (failed)
            cancelAcquire(node);
    }
}

This is a cyclic spin operation. Before blocking the thread, first determine whether the headnode in front of the thread itself is a node. If so, re-acquire the lock. After the acquisition is successful, return and cancel the continuous acquisition process. If not, call the shouldParkAfterFailedAcquiremethod to judge whether the current thread should be blocked, mainly through the node waitStatusto judge.

FairSync source code analysis

The implementations of fair locks FairSyncand unfair locks NonFairSyncare very similar. Let's compare the differences between the two.

  • FairSyncIn the lockmethod, there is no way to obtain the lock NonFairSyncthrough the CASoperation first state, but tryAcquireto obtain the lock directly.
final void lock() {
    acquire(1);
}
  • FairSyncIn the process of acquiring the lock, the version tryAcquireneeds to first determine whether there are other waiting threads in the queue, and if not, go back and try to acquire the lock.
/**
 * Fair version of tryAcquire.  Don't grant access unless
 * recursive call or no waiters or is first.
 */
protected final boolean tryAcquire(int acquires) {
    final Thread current = Thread.currentThread();
    int c = getState();
    if (c == 0) {
        if (!hasQueuedPredecessors() &&
            compareAndSetState(0, acquires)) {
            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;
}

unlock() releases the lock

Release locks do not distinguish between fair and unfair. The main job is to reduce statethe value. When statewaiting for 0, release the lock and wake up other threads in the team to acquire the lock.

public void unlock() {
    sync.release(1);
}
public final boolean release(int arg) {
    if (tryRelease(arg)) {
        Node h = head;
        if (h != null && h.waitStatus != 0)
            unparkSuccessor(h);
        return true;
    }
    return false;
}
protected final boolean tryRelease(int 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;
}

Summarize

  • ReentrantLockIt is AQSa statefield used to determine whether it is occupied.
  • 公平The 非公平difference is whether the way to acquire locks is in order.
  • stateActions are CASimplemented through. A queue that is blocked due to a preemptive lock is implemented through a queue.
  • In the process of blocking the thread, AQSthere is a process of spinning, not directly blocking if the lock is not obtained.

Guess you like

Origin http://43.154.161.224:23101/article/api/json?id=326154806&siteId=291194637