preface

ReentrantLock: ReentrantLock: ReentrantLock: ReentrantLock: ReentrantLock: ReentrantLock: ReentrantLock: ReentrantLock: ReentrantLock: ReentrantLock: ReentrantLock

If read star wrote before the word long | AbstractQueuedSynchronizer 16 picture untie, the effects of this full, did not read it doesn’t matter, still can get to the already principle.

Know already under

A Xing first take readers and ReentrantLock to meet, a simple understanding of what is ReentrantLock.

ReentrantLock is a reentrant mutex that has the same functionality as synchronized, but is more flexible (with more methods) than synchronized.

Already the bottom based on AbstractQueuedSynchronizer implementation, AbstractQueuedSynchronizer in detailed anatomy have been made on the previous, this article does not do too much description, but simple introduce, to take care of the little white.

AbstractQueuedSynchronizer abstract class defines a set of the synchronization of multithreaded access to a Shared resource template, solved the realization of synchronizer involved when a large number of details, can greatly reduce the implementation work, with the old vernacular, AbstractQueuedSynchronizer for locking and unlocking process provides a unified template function, only a small amount of detail by subclasses decide for themselves.

After the above introduction, I believe that readers have a preliminary impression of ReentrantLock, the next start ~

ReentrantLock structure

A Xing feels that the first thing to learn any knowledge is to see its full picture, tease out the overall structure and main process, and then break it one by one. So a Xing takes readers to look at the overall structure of ReentrantLock first, and have a general understanding of its implementation.

ReentrantLock implements the Lock interface. The Lock interface is a unified specification for the behavior of Lock operations in Java. Abiding by rules and norms is the basic quality of law-abiding citizens, which is reasonable and reasonable. The definition of Lock interface is as follows


public interface Lock {

    /** * Get the lock */
    void lock(a);

    /** * Get lock - response interrupt */
    void lockInterruptibly(a) throws InterruptedException;

    /** * Returns the status of obtaining the lock successfully */
    boolean tryLock(a);

    /** * Return the status of obtaining the lock successfully - Response interrupted */
    boolean tryLock(long time, TimeUnit unit) throws InterruptedException;

    /** * Release the lock */
    void unlock(a);

    /** * Create condition variable */
    Condition newCondition(a);
}

Copy the code

The Lock interface defines only a few functions. ReentrantLock implements these functions. ReentrantLock follows the decouple and extensible design. Sync inheritance AbstractQueuedSynchronizer provide release resources, NonfairSync and FairSync is based on the Sync extension subclass, which already of fairness and justice, they function as the Lock interface basic implementation.

Old vernacular, the boss of the enterprise, in response to the government’s policy, the need to make adjustment in the enterprise interior, but the government policy is different every year, every time want oneself to do, just a long pain rather short pain, set up a policy response to the department, after these things were given to the department to do, the boss just need to command them.

ReentrantLock structure composition readers are also clear, the following only need to Sync, NonfairSync, FairSync break one by one, ReentrantLock naturally comes naturally.

Tip: In already, it is the state of AbstractQueuedSynchronizer status value is defined as the thread to get the number of reentrant lock, state value of 0 indicates the current state is not held any threads, the state said by other threads to hold state value is 1, because of the support reentrant, If the thread that holds the lock obtains the same lock again, the thread succeeds directly and the state value of the lock is +1, the thread releases the lock with the state value of -1. Similarly, if the thread reenters the lock multiple times, the thread needs to release the corresponding number of times.

Sync

Sync is already kiss the son, it is the hope of the village, perfect inherited AbstractQueuedSynchronizer, is the core of the already, The following NonfairSync and FairSync subclasses are based on extensions of Sync.

Now that a Xing has finished playing Sync, let’s take a look at the core of the Sync class definition


abstract static class Sync extends AbstractQueuedSynchronizer {
        private static final long serialVersionUID = -5179523762034025860L;

        /** * Acquire lock - subclass implementation */
        abstract void lock(a);

        /** * Unfair - Access to resources */
        final boolean nonfairTryAcquire(int acquires) {
            // Get the current thread
            final Thread current = Thread.currentThread();
            // Get the current state
            int c = getState();
            if (c == 0) { // State ==0 indicates that resources are available
                // Cas sets state to acquires and acquires to 1
                if (compareAndSetState(0, acquires)) {
                    // Cas successfully set the thread that currently holds the lock
                    setExclusiveOwnerThread(current);
                    // Return success
                    return true; }}else if (current == getExclusiveOwnerThread()) { / / if the state! =0, but the current thread is the thread holding the lock, direct reentrant
                / / state state + 1
                int nextc = c + acquires;
                if (nextc < 0) // overflow
                    throw new Error("Maximum lock count exceeded");
                // Set the state, where cas is not required because only one thread holds the lock
                setState(nextc);
                // Return success
                return true;
            }
            // Failed to return
            return false;
        }
        
        /** * Release resources */
        protected final boolean tryRelease(int releases) {
            // State state -releases 1
            int c = getState() - releases;
            if(Thread.currentThread() ! = getExclusiveOwnerThread())// Throw an exception if the current thread is not holding the lock
                throw new IllegalMonitorStateException();
            // Set return status, default is failed
            boolean free = false;
            if (c == 0) {If c==0 after state-1, resources are released successfully
                // Set the return status to true
                free = true;
                // Clear the thread holding the lock
                setExclusiveOwnerThread(null);
            }
            If the value of state is >0, the current thread has a lock reentrant operation, and the number of times the lock must be released, set the value of state
            setState(c);
            returnfree; }}Copy the code

Sync is A little bit off base. First, the Sync implementation releases the details of the resource (A Q S for the subclass implementation tryRelease). Then, the subclass declared the abstract function (lock) to acquire the lock. However, Sync also defines a nonfairTryAcquire function, which is intended for NonfairSync. FairSync does not have this treatment, so Sync is biased.

The Sync logic is simple. It implements tryRelease for classes A, Q, and S, and then abstracts A lock acquisition function for subclasses to implement by themselves. In addition, it also adds A nonfairTryAcquire function.

Let’s put a tryRelease flow chart below. There will be a comprehensive process in subsequent NonfairSync and FairSync.

NonfairSync

Now let’s turn to NonfairSync, which supports two lock acquisition strategies in ReentrantLock. One is an unfair policy and the other is a fair policy. NonfairSync is an unfair policy.

At this point, readers will ask, what is a Xing’s unfair strategy?

Before said the fair strategy under the simple said A Q S (AbstractQueuedSynchronizer) process, A Q S for locking and unlocking process provides A unified template function, lock and unlock the template process, failed to get the lock of the thread, will enter the CLH queue blocked, Unlocking other threads wakes up the CLH queue thread, as shown in the figure below (simplifying the process)

Above, thread lock is released, will wake CLH queue blocked thread and lock to competition, to note that at this time there may be not the CLH queue of threads to participate in the competition, so the fair is here, the CLH queue thread with CLH queue thread competition, each on merit, not because of who you are, but because CLH queue of threads, stood in line for a long time, just lock to you.

Now that we know what is an unfair policy, let’s look at the NonfairSync class definition

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

        /** * Get the lock */
        final void lock(a) {
            if (compareAndSetState(0.1))// If state is set to 1, the cas resource is successfully obtained
                // Set the current thread to the thread holding the lock
                setExclusiveOwnerThread(Thread.currentThread());
            else
                // If the CAS fails to set state to 1, resources fail to be obtained. Run the AQS process to obtain the lock template. If no, resources are successfully obtained
                acquire(1);
        }
        
        /** * Acquire resources - using the nonfairTryAcquire function provided by Sync */
        protected final boolean tryAcquire(int acquires) {
            returnnonfairTryAcquire(acquires); }}/** * AQS acquires the lock template function, which is a function in the AQS class */
    public final void acquire(int arg) {
        /** * We only need to pay attention to tryAcquire function, the following function is the details of the process of AQS failed to acquire resources, the thread node into the CLH queue, this article does not pay attention to */
        if(! tryAcquire(arg) && acquireQueued(addWaiter(Node.EXCLUSIVE), arg)) selfInterrupt(); }Copy the code

NonfairSync inheritance Sync lock function is achieved, the lock function is also very simple, C A S the state sets the status value of 1 for acquiring A lock, success or to perform A Q S acquire function (acquiring A lock template), In addition, NonfairSync implements A tryAcquire function, which is left to subclasses of A Q S to implement. This one will use the tryfairTryacquire function provided by Sync directly. Finally, the subclass implementation of tryAcquire is used in the acquire function of A Q S.

Isn’t that a little convoluted? Nothing, a star with everyone together with a wisp

First of all, A Q S acquire function is A process template for acquiring locks. The template process will execute tryAcquire function to acquire resources first. TryAcquire function is implemented by subclass NonfairSync, which implements tryAcquire function. The implementation is a call to Sync’s nonfairTryAcquire function.

Next, let’s take a look at the nonfairTryAcquire function logic Sync provides for NonfairSync

    /** * Unfair - Access to resources */
    final boolean nonfairTryAcquire(int acquires) {
        // Get the current thread
        final Thread current = Thread.currentThread();
        // Get the current state
        int c = getState();
        if (c == 0) { // State ==0 indicates that resources are available
            // Cas sets state to acquires and acquires to 1
            if (compareAndSetState(0, acquires)) {
                // Cas successfully set the thread that currently holds the lock
                setExclusiveOwnerThread(current);
                // Return success
                return true; }}/ / if the state! =0, but the current thread is the thread holding the lock, direct reentrant
        else if (current == getExclusiveOwnerThread()) {
            / / state state + 1
            int nextc = c + acquires;
            if (nextc < 0) // overflow
                throw new Error("Maximum lock count exceeded");
            // Set the state, where cas is not required because only one thread holds the lock
            setState(nextc);
            // Return success
            return true;
        }
        // Failed to return
        return false;
    }
Copy the code

The current thread checks to see if resources are available:

  • Available, try to useC A SSet up thestatefor1.C A SIf the resource is successfully obtained, the resource fails to be obtained
  • If not, determine if the thread is holding the lock. If so,stateReentrant count indicates that the resource is successfully obtained. Otherwise, the resource fails to be obtained

Just two sentences, isn’t it very simple? Though simple, a Xing still drew a flow chart of nonfairTryAcquire for readers to appreciate

FairSync

Where there is an unfair policy, there is a fair policy, and FairSync is the fair policy of ReentrantLock.

The so-called fair strategy is, in strict accordance with the CLH queue in order to obtain the lock, thread lock is released, will wake CLH queue blocked thread and lock to competition, to note that at this time there may be not the CLH queue of threads to participate in the competition, in order to ensure fair, will make the CLH queue thread competitive success, if not the CLH queue thread has been holding time, It will continue to fail (build nodes inserted into the end of the CLH queue, executed by A S Q template process) until the time slice turns on the CLH queue thread, so the fairness policy will perform worse.

Now that we know what a fair policy is, let’s take a look at the FairSync class definition


static final class FairSync extends Sync {
        private static final long serialVersionUID = -3000897897090466540L;
        
        /** * Get the lock */
        final void lock(a) {
        // If the CAS fails to set state to 1, resources fail to be obtained. Run the AQS process to obtain the lock template. If no, resources are successfully obtained
            acquire(1);
        }

        /** * Get resources */
        protected final boolean tryAcquire(int acquires) {
            // Get the current thread
            final Thread current = Thread.currentThread();
            // Obtain the state status
            int c = getState();
            if (c == 0) { // State ==0 indicates that resources are available
                / / 1. HasQueuedPredecessors to judge whether the current thread CLH queue awakened threads, if the next step is to perform
               //2. Set state to acquires and enter 1 as acquires
                if(! hasQueuedPredecessors() && compareAndSetState(0, acquires)) {
                    // Cas successfully set the thread that currently holds the lock
                    setExclusiveOwnerThread(current);
                    // Return success
                    return true; }}/ / if the state! =0, but the current thread is the thread holding the lock, direct reentrant
            else if (current == getExclusiveOwnerThread()) {
                / / state state + 1
                int nextc = c + acquires;
                if (nextc < 0)
                    throw new Error("Maximum lock count exceeded");
                // Set the state, where cas is not required because only one thread holds the lock
                setState(nextc);
                // Return success
                return true;
            }
            return false; }}/** * AQS acquires the lock template function, which is a function in the AQS class */
    public final void acquire(int arg) {
        /** * We only need to pay attention to tryAcquire function, the following function is the details of the process of AQS failed to acquire resources, the thread node into the CLH queue, this article does not pay attention to */
        if(! tryAcquire(arg) && acquireQueued(addWaiter(Node.EXCLUSIVE), arg)) selfInterrupt(); }Copy the code

It is easy to find that the FairSync process is basically the same as NonfairSync, the only difference is that before C A S execution, there is A step hasqueuedtransformer function, this step is to determine whether the current thread is the CLH queue woke up thread, if so, the execution of C A S, Otherwise the resource fails to be obtained, as shown in the following diagram

The realization of the Lock

Finally, let’s see how to implement Lock in ReentrantLock, first look at the constructor part

    / / synchronizer
    private final Sync sync;
    
    // An unfair policy is used by default
    public ReentrantLock(a) {
        sync = new NonfairSync();
    }

    //true- Fair policy false Non-fair policy
    public ReentrantLock(boolean fair) {
        sync = fair ? new FairSync() : new NonfairSync();
    }
Copy the code

By default, ReentrantLock uses an unfair policy. If you want to specify a mode, you can select it by taking a fair argument. Without going into the overview, let’s look at ReentrantLock’s implementation of Lock

public class ReentrantLock implements Lock.java.io.Serializable {
    private static final long serialVersionUID = 7373984872572414699L;
    / / synchronizer
    private final Sync sync;

    // An unfair policy is used by default
    public ReentrantLock(a) {
        sync = new NonfairSync();
    }

    //true- Fair policy false Non-fair policy
    public ReentrantLock(boolean fair) {
        sync = fair ? new FairSync() : new NonfairSync();
    }

    /** * Acquire lock - block */
    public void lock(a) {
        // Based on sync
        sync.lock();
    }

    /** * Acquire lock - block, support response thread interrupt */
    public void lockInterruptibly(a) throws InterruptedException {
        // Based on sync
        sync.acquireInterruptibly(1);
    }

    /** * Obtain the resource and return success status - non-blocking */
    public boolean tryLock(a) {
        // Based on sync
        return sync.nonfairTryAcquire(1);
    }

    /** * Acquire lock - block, support timeout */
    public boolean tryLock(long timeout, TimeUnit unit)
            throws InterruptedException {
        // Based on sync
        return sync.tryAcquireNanos(1, unit.toNanos(timeout));
    }

    /** * Release the lock */
    public void unlock(a) {
        // Based on sync
        sync.release(1);
    }

    /** * Create condition variable */
    public Condition newCondition(a) {
        // Based on sync
        returnsync.newCondition(); }}Copy the code

Is not particularly simple, ReentrantLock Lock implementation is based on Sync to do, there is an artifact in hand, the world I have style.

Sync contracted for everything, why it is so cowhide, because have the Sync AbstractQueuedSynchronizer big brother hood, under NonfairSync and FairSync can send two younger brother, so be already very reasonable.

Finally, A xinggan is A flow chart combining A, Q and S to end ReentrantLock.

chatter

This time a xing wrote in the style slightly adjusted, I don’t know if you readers like it, if you like, please like, look again, let a xing feel the enthusiasm of the readers, finally, there will be a survey on the reading time, also hope that readers can participate in the enthusiasm, so that a Xing on the time to post a B tree in mind.

Good historical articles recommended

  • Word long | 16 picture AbstractQueuedSynchronizer
  • LockSupport
  • 13 images for an in-depth understanding of Synchronized
  • From shallow to deep CAS, Xiao Bai can also connect with BAT interviewers
  • Small white can also understand the Java memory model
  • Nanny level teaching, 22 diagrams uncover ThreadLocal
  • Are processes, threads and coroutines indistinguishable? Take you through a article!
  • What is thread-safe? This article gives you an in-depth understanding

About me

Here is a star, a love of technology Java program ape, the public number “program ape a star” will regularly share operating system, computer network, Java, distributed, database and other original articles, 2021, and you in Be Better on the way to grow together! .

Thank you very much for seeing here, the original is not easy, the article can help attention, point a like, share and comment, are all support (don’t want white prostitute)!

I hope you and I can go on the way we want to go, see you in the next article