Detailed implementation of a java lock reentrant lock ReentrantLock

Posted by chowdary on Fri, 19 Jul 2019 02:19:55 +0200

1. Features of ReentrantLock

ReentrantLock is a reentrant mutex provided in a Java concurrent package.ReentrantLock and synchronized are similar in basic usage, behavioral semantics, and reentrant as well.ReentrantLock adds some advanced extensions to its native Synchronized functionality, such as fair locking and the ability to bind multiple Conditon s.

Reentrant:

Repeated acquisition and release of locks by a thread can be supported.Native synchronized is reentrant, a recursive method decorated with synchronized that allows a thread to acquire locks repeatedly during execution without locking itself.The same is true for ReentrantLock, which, when the lock() method is called, acquires the lock thread, can call the lock() method again to acquire the lock without blocking, and locks and unlock s must be called equally many times to release the lock.

Fair/unfair locks:

Fair lock: refers to the FIFO method of acquiring and releasing locks in the same order in which threads acquire locks as locks are invoked.

Unfair locks: The order in which threads acquire locks is independent of the order in which locks are invoked. Whether or not locks can be acquired depends on the timing of the invocation.

synchronized is an unfair lock and ReentrantLock is unfair by default, but fair locks can be specified by a construction method with boolean parameters, but they generally perform better than fair locks.

2. ReentrantLock implementation

All lock-related operations of ReentrantLock are implemented through the Sync class, which inherits from the AbstractQueuedSynchronizer synchronization queue and implements some common interface implementations.

NonfairSync inherits from Sync and implements an unfair way to acquire locks;

FairSync inherits from Sync and achieves a fair way to acquire locks;

The ReentrantLock class implementation structure is as follows:

ReentrantLock class implementation structure.png

2.1, Sync implementation

//sync inherits from AQS
abstract static class Sync extends AbstractQueuedSynchronizer {
    private static final long serialVersionUID = -5179523762034025860L;

    //Abstract method, subclass implementation required
    abstract void lock();

    //Unfair acquisition of locks implemented
    final boolean nonfairTryAcquire(int acquires) {
        final Thread current = Thread.currentThread();
        //Gets the current lock state, which c>0 indicates that a thread has acquired a lock, and the number of re-entries is C
        int c = getState();
        //Wireless access lock?
        if (c == 0) {
            //CAS acquires locks, success sets thread to acquire exclusive locks
            if (compareAndSetState(0, acquires)) {
                setExclusiveOwnerThread(current);
                return true;
            }
        }
        //Is the thread that gets the lock the current thread?Get the number of times plus 1, and set the status (that is, the number of times)
        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;
    }

    //Release lock
    protected final boolean tryRelease(int releases) {
        //Calculate status values that need to be updated
        int c = getState() - releases;
        //Throw an exception if the current thread does not get a lock
        if (Thread.currentThread() != getExclusiveOwnerThread())
            throw new IllegalMonitorStateException();
        boolean free = false;
        //A status value of 0 indicates the last lock released by the current thread.
        //The lock will then be truly released and acquired by other threads;
        //Otherwise, it means that the current thread has acquired more locks than released them
        if (c == 0) {
            free = true;
            //Threads clearing exclusive locks on records
            setExclusiveOwnerThread(null);
        }
        //Update Status
        setState(c);
        return free;
    }

    //Determine current thread release to acquire exclusive locks
    protected final boolean isHeldExclusively() {
       //The current thread acquires an exclusive lock for a recorded thread, indicating that the current thread acquires an exclusive lock
        return getExclusiveOwnerThread() == Thread.currentThread();
    }

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

    
    //Gets the thread object that acquires the exclusive lock
    final Thread getOwner() {
        return getState() == 0 ? null : getExclusiveOwnerThread();
    }

    //Gets the number of times the current thread has acquired a lock
    final int getHoldCount() {
        return isHeldExclusively() ? getState() : 0;
    }
    
    //Determine if a lock is occupied
    final boolean isLocked() {
        return getState() != 0;
    }
}

2.2. Implementation of Unfair Lock NonfairSync

//An implementation of the unfair lock NonfairSync, which inherits from Sync
static final class NonfairSync extends Sync {
    private static final long serialVersionUID = 7316153563782823691L;

    //lock interface implementation;
    //First, CAS attempts to acquire the lock, and then Owner sets the lock if it succeeds.
    //Otherwise, call acquire to acquire the lock, or call tryAcquire to acquire the lock.
    //tryAcquire acquires locks in an unfair manner.
    final void lock() {
        if (compareAndSetState(0, 1))
            setExclusiveOwnerThread(Thread.currentThread());
        else
            acquire(1);
    }

    //Unfair Lock Acquisition
    protected final boolean tryAcquire(int acquires) {
        return nonfairTryAcquire(acquires);
    }
}

2.3. Implementation of FairSync with Fair Lock

//FairLock FairSync implementation, which inherits from Sync
static final class FairSync extends Sync {
    private static final long serialVersionUID = -3000897897090466540L;

    //Lock interface implementation, call acquire to acquire the lock by itself;
    //Acquire then calls tryAcquire to acquire the lock, while tryAcquire is through Fairness (FIFO)
    //Way to acquire a lock.
    final void lock() {
        acquire(1);
    }

    //Acquire locks fairly
    protected final boolean tryAcquire(int acquires) {
        final Thread current = Thread.currentThread();
        //Gets the current lock state, which c>0 indicates that a thread has acquired a lock, and the number of re-entries is C
        int c = getState();
        //Wireless access lock?
        if (c == 0) {
            //The current node has no precursor node and the current thread CAS update status is successful;,
            //Indicates fair acquisition of locks by the current thread
            if (!hasQueuedPredecessors() &&
                compareAndSetState(0, acquires)) {
                setExclusiveOwnerThread(current);
                return true;
            }
        }
        //Is the thread that gets the lock the current thread?Get the number of times plus 1, and set the status (that is, the number of times)
        else if (current == getExclusiveOwnerThread()) {
            int nextc = c + acquires;
            if (nextc < 0)
                throw new Error("Maximum lock count exceeded");
            setState(nextc);
            return true;
        }
        return false;
    }
}

2.4. Implementation principle of locks reentrant

From the source code analysis above, when a node acquires a lock, it records the thread Thread that acquired the exclusive lock through the setExclusiveOwnerThread() method; when a thread acquires a lock, when the lock is occupied, it determines whether the thread that occupies the lock is the current thread; if it updates the lock status directly, it indicates that the lock was acquired;Otherwise, acquisition of the lock failed.

2.5. Realization Principles of Lock Fairness and Inequality

FairSync implements a fair lock, which when a lock is acquired by tryAcquire, determines whether the current node in the synchronization queue has a precursor node; if a precursor node exists, it fails to acquire the lock, enters the synchronization queue and waits for the lock to be acquired; when no precursor node exists, it indicates that the current node is the node in the synchronization queue that has the longest waiting time for the lock.The current node takes precedence over acquiring lock resources.

Unfair locks are implemented through NonfairSync, which attempts to acquire locks through CAS before they enter the synchronization queue and wait for them to be acquired.This causes the current thread to acquire locks through CAS first at the synchronization queue header node when a thread has just released the lock and the unpark header node in the synchronization queue has not yet acquired the time gap for the lock.It is unfair for some threads to wait a long time for a lock to be acquired.

The ReentrantLock is constructed as follows:

//A parameterless construction method that implements locks in an unfair manner
public ReentrantLock() {
    sync = new NonfairSync();
}

//Construction method with parameters;
//fire=true: locks are constructed fairly;
//fire=false: locks are constructed in an unfair manner;
public ReentrantLock(boolean fair) {
    sync = fair ? new FairSync() : new NonfairSync();
}

Topics: Java