ReentrantLock source code analysis

When we read the source code of ReentrantLock, we found the following three classes:

Among them: There are three classes: Sync, NonfairSync, and FairSync inside the ReentrantLock class. The NonfairSync and FairSync classes inherit from the Sync class, and the Sync class inherits from the AbstractQueuedSynchronizer abstract class. Of course, the ReentrantLock class itself inherits the Lock interface.

Lock

The lock interface defines five methods commonly used in concurrency:

public interface Lock {
    // Get the lock
    void lock();

    // Obtaining the lock can be interrupted, that is, interrupt can be interrupted during the lock acquisition process. The difference is that synchronized locks cannot be interrupted.
    void lockInterruptibly() throws InterruptedException;

    //Try to acquire the lock. The lock can only be acquired when the lock is idle (it will not wait if the lock is not acquired)
    boolean tryLock();

    //Try to acquire the lock within a given time, returning true if successful and false if failed.
    boolean tryLock(long time, TimeUnit unit) throws InterruptedException;

    // release lock
    void unlock();

    //Waiting and wake-up mechanism
    Condition newCondition();
}

ReentrantLock inherits the lock interface, and these methods will also be rewritten inside ReentrantLock.

Sync

abstract static class Sync extends AbstractQueuedSynchronizer {

    private static final long serialVersionUID = -5179523762034025860L;

    // Get the lock
    abstract void lock();

    //Acquire unfair lock
    final boolean nonfairTryAcquire(int acquires) {
        // Get the current thread
        final Thread current = Thread.currentThread();
        // Get status
        int c = getState();
        if (c == 0) { //Indicates that no thread is competing for the lock
            //Try to get the lock through CAS, status 0 means the lock is not occupied!!!
            if (compareAndSetState(0, acquires)) {
                //Set the current thread exclusive, that is, set the current thread exclusive lock
                setExclusiveOwnerThread(current);
                return true; // success
            }
        }
        //If it is locked, further determine that the current thread owns the lock (that is, reentrant lock)
        else if (current == getExclusiveOwnerThread()) {
            int nextc = c + acquires; // Increase the number of reentries
            if (nextc < 0) // overflow
                throw new Error("Maximum lock count exceeded");
            //Set status
            setState(nextc);
            // success
            return true;
        }
        // fail
        return false;
    }

    //Try to release resources
    protected final boolean tryRelease(int releases) {
        int c = getState() - releases;
        if (Thread.currentThread() != getExclusiveOwnerThread()) // The current thread is not an exclusive thread
            throw new IllegalMonitorStateException();
        // Release flag
        boolean free = false;
        if (c == 0) {
            free = true;
            // Already released, clear exclusive use
            setExclusiveOwnerThread(null);
        }
        //Set the flag
        setState(c);
        return free;
    }

    // Determine whether the resource is occupied by the current thread
    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();
    }

    // Create a new condition
    final ConditionObject newCondition() {
        return new ConditionObject();
    }

    // Get the thread holding the lock
    final Thread getOwner() {
        return getState() == 0 ? null : getExclusiveOwnerThread();
    }
    //return status
    final int getHoldCount() {
        return isHeldExclusively() ? getState() : 0;
    }

    //Whether it is locked
    final boolean isLocked() {
        return getState() != 0;
    }

    /**
        * Reconstitutes the instance from a stream (that is, deserializes it).
        */
    // Custom deserialization logic
    private void readObject(java.io.ObjectInputStream s)
    throws java.io.IOException, ClassNotFoundException {
        s.defaultReadObject();
        setState(0); // reset to unlocked state
    }
}

Sync is an abstract class, so there must be classes that inherit it. There are two Sync implementations in ReentrantLock, namely NonfairSync and FairSync.

NonfairSync and FairSync

static final class NonfairSync extends Sync {
        private static final long serialVersionUID = 7316153563782823691L;

        /**
         * Performs lock. Try immediate barge, backing up to normal
         * acquire on failure.
         */
        final void lock() {
         // Use CAS to lock (if state is equal to 0, set it to 1 and return true, otherwise return false),
            if (compareAndSetState(0, 1))
             // If the lock is successful, set the exclusive thread as the current thread
                setExclusiveOwnerThread(Thread.currentThread());
            else
             // If the lock fails, call the acquire method of the AbstractQueuedSynchronizer class.
                acquire(1);
        }

        protected final boolean tryAcquire(int acquires) {
         // Call the nonfairTryAcquire method of the parent class Sync
            return nonfairTryAcquire(acquires);
        }

    }

We can see that in unfair locks, if the current thread lock occupancy status is 0, CAS will be directly used to try to acquire the lock. There is no need to join the queue, and then wait for the queue head thread to wake up and then acquire the lock, so the efficiency is relatively high. It will be faster than fair lock. ReentrantLock is an unfair lock by default

Let’s look at the source code of FairSync:

static final class FairSync extends Sync {
        private static final long serialVersionUID = -3000897897090466540L;

        final void lock() {
            acquire(1);
        }

        /**
         * 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) {//unlocked state
                //First determine that there is no waiting node, then open CAS to get the lock
                if (!hasQueuedPredecessors() & amp; & amp;
                    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;
        }
    }

We can see that in fair lock, if the current thread lock occupancy status is 0, it will first synchronize whether there is a waiting thread in the queue. If not, it will perform the lock operation, thus following the FIFO principle. First come, first served. Therefore, the efficiency is slower than unfair lock. Within ReentrantLock, both the NonfairSync, FairSync, and Sync classes ultimately inherit AbstractQueuedSynchronizer. This is also a very important part. Let’s take a look at it together.