This is the third day of my participation in Gwen Challenge

ReentrantLock ReentrantLock ReentrantLock ReentrantLock ReentrantLock ReentrantLock ReentrantLock

1. What is ReentrantLock?

ReentrantLock is a reentrant mutex Lock that has some of the same basic behavior and semantics as implicit monitor locks accessed using synchronized methods and statements, but is more powerful. The ReentrantLock will be owned by the thread that recently successfully acquired the lock and has not released the lock. When the lock is not owned by another thread, the thread calling the lock successfully acquires the lock and returns. This method returns immediately if the current thread already owns the lock. You can use the isHeldByCurrentThread() and getHoldCount() methods to check if this happens.

ReentrantLock ReentrantLock ReentrantLock ReentrantLock ReentrantLock ReentrantLock ReentrantLock ReentrantLock ReentrantLock In both cases, the same thread does not enter the lock once, and the lock counter increases by 1, so the lock can be released only when the lock counter decreases to 0.

ReentrantLock’s organizational structure:

public class ReentrantLock implements Lock.java.io.Serializable {
    private static final long serialVersionUID = 7373984872572414699L;
    /** Synchronizer providing all implementation mechanics */
    private final Sync sync;

    /** * Base of synchronization control for this lock. Subclassed * into fair and nonfair versions below. Uses AQS state to * represent the number of holds on the lock. */
    abstract static class Sync extends AbstractQueuedSynchronizer {}/** * Sync object for non-fair locks */
    static final class NonfairSync extends Sync {}/** * Sync object for fair locks */
    static final class FairSync extends Sync {}public ReentrantLock(a) {
        sync = new NonfairSync();
    }

    public ReentrantLock(boolean fair) {
        sync = fair ? new FairSync() : newNonfairSync(); }}Copy the code

It can be seen that:

  • ReentrantLockTo achieve theLockInterface.
  • ReentrantLocksyncIt’s a combinatorial relationship.ReentrantLockContainsSyncObject; Moreover,SyncAQSThe subclass. And more importantly,SyncThere are two subclassesFairSync(Fair lock) andNonFairSync(Unfair lock).ReentrantLockIs an exclusive lock, and whether it is a fair lock or an unfair lock dependssyncThe object isFairSyncOrNonFairSyncThe instance.

2. ReentrantLock source code parsing

Since ReentrantLock is a lock, let’s look at its basic structure:

public class ReentrantLock implements Lock.java.io.Serializable {
    private static final long serialVersionUID = 7373984872572414699L;
    /** Synchronizer providing all implementation mechanics */
    private finalSync sync; . Omit N more code hereCopy the code

Quite simply, it contains a Sync class object inside. What about the Sync class?

2.1 Sync class analysis

/** * Base of synchronization control for this lock. Subclassed * into fair and nonfair versions below. Uses AQS state to * represent the number of holds on the lock. */
abstract static class Sync extends AbstractQueuedSynchronizer {
    private static final long serialVersionUID = -5179523762034025860L;

    /**
     * Performs {@link Lock#lock}. The main reason for subclassing
     * is to allow fast path for nonfair version.
     */
    abstract void lock(a);

    /** * Performs non-fair tryLock. tryAcquire is * implemented in subclasses, but both need nonfair * try for trylock method. */
    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;
    }

    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;
    }

    protected final boolean isHeldExclusively(a) {
        // 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();
    }

    final ConditionObject newCondition(a) {
        return new ConditionObject();
    }

    // Methods relayed from outer class

    final Thread getOwner(a) {
        return getState() == 0 ? null : getExclusiveOwnerThread();
    }

    final int getHoldCount(a) {
        return isHeldExclusively() ? getState() : 0;
    }

    final boolean isLocked(a) {
        returngetState() ! =0;
    }

    /**
     * Reconstitutes this lock instance from a stream.
     * @param s the stream
     */
    private void readObject(java.io.ObjectInputStream s)
        throws java.io.IOException, ClassNotFoundException {
        s.defaultReadObject();
        setState(0); // reset to unlocked state}}Copy the code

As you can see from the class’s comments, Sync inherits the AQS class and is the basis for synchronizing the ReentrantLock class, which has two subclasses, Fair and Nonfair. The state number value of the AQS class is used to record whether the lock is held. Basic composition:

  • abstract void lock()
  • boolean nonfairTryAcquire(int acquires)
  • boolean tryRelease()
  • boolean isHeldExclusively()
  • Thread getOwner()
  • getHoldCount()

The Sync class declares the abstract method Lock for subclasses to implement. Sync class implements lock acquisition, release, hold and other methods.

2.1.1 NonfairSync Unfair lock

/** * Sync object for non-fair locks */
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(a) {
        if (compareAndSetState(0.1))
            setExclusiveOwnerThread(Thread.currentThread());
        else
            acquire(1);
    }

    protected final boolean tryAcquire(int acquires) {
        returnnonfairTryAcquire(acquires); }}Copy the code

Sync is inherited from unfair locks and the lock method is implemented. The state is first obtained using the compareAndSetState() method and, if successful, the setExclusiveOwnerThread is set to the thread that owns 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);
}
Copy the code

The Unsafe CAS method essentially calls the compareAndSwapInt method, which is implemented as follows:

/**
 * Atomically update Java variable to <tt>x</tt> if it is currently
 * holding <tt>expected</tt>.
 * @return <tt>true</tt> if successful
 */
public final native boolean compareAndSwapInt(Object o, long offset,
                                              int expected,
                                              int x);
Copy the code

As you can see, this is not implemented in Java, but rather a native program that calls the operating system through JNI. So take a look at its JNI implementation:

UNSAFE_ENTRY(jboolean, Unsafe_CompareAndSwapInt(JNIEnv *env, jobject unsafe, jobject obj, jlong offset, jint e, jint x))
  UnsafeWrapper("Unsafe_CompareAndSwapInt");
  oop p = JNIHandles::resolve(obj);
  jint* addr = (jint *) index_oop_from_field_offset_long(p, offset);
  return (jint)(Atomic::cmpxchg(x, addr, e)) == e;
UNSAFE_END
Copy the code

You can see that the CMPXCHG method of the Atomic class is actually called. The implementation of the Atomic CMPXCHG class is OS dependent and CPU architecture-dependent, For Windows x86, the implementation is in the atomic_windows_x86.inline. HPP file in the hotspot SRC \ os_CPU \ windows_x86. vm\ directory.

inline jint     Atomic::cmpxchg    (jint     exchange_value, volatile jint*     dest, jint     compare_value) {
  // alternative for InterlockedCompareExchange
  int mp = os::is_MP();
  __asm {
    mov edx, dest
    mov ecx, exchange_value
    mov eax, compare_value
    LOCK_IF_MP(mp)
    cmpxchg dword ptr [edx], ecx
  }
}
Copy the code

As you can see here, implemented in embedded assembly, the key CPU instruction is CMPXCHG and you can’t go any further than that. That is, the atomicity of CAS is actually implemented by the CPU. In fact, there is an exclusive lock at this point. It’s just a lot shorter than synchronized. So performance is better in multithreaded cases.

In the second step, if the CAS operation fails, acquire the lock through the acquire method.

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

In the acqurie() method, the tryAcquire() method is first used for quick application judgment.

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

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;
}
Copy the code

Here we can see a quick lock request through tryAcquire() in the acquire method, and we can see that inside tryAcquire is the nonfairTryAcquire() method of the Sync class that was called. The logic of nonfairTryAcquire is as follows:

  • First of all bygetState()Gets the synchronizer status value if the status value is0, that is, if the current lock is idle, the current thread holds the lock.
  • If the current thread already has the lock, the current threadstatefor+acquires.

2.1.2 Fair Lock FairSync

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

    final void lock(a) {
        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) {
            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; }}Copy the code

In the implementation of the lock method of fair lock, it can also be seen that the state of the lock can be obtained directly through acquire(1) method, and finally the state of the lock can be guaranteed through CAS method.

ReentrantLock supports both fair and unjust places, and internally defines instances of the Sync class that need to be created based on the requirements passed in the constructor.

2.1.3 summary

When applying for a Lock, a fair Lock is directly applied for through acquire method. The underlying call of acquire method is tryAcquire method. In tryAcquire method, if the Lock is idle, it returns true to immediately obtain the Lock. If the current thread already holds the lock, the lock counter +1, otherwise false is returned. Then through the acquireQueued method loop, the application is not added to the waiting CLH queue. In an unfair lock, the system checks whether the current lock is free. If so, the system directly applies for the lock regardless of the thread in the waiting queue. Otherwise, if it is possessed, the acquire method is called.

2.2 ReentrantLock Method Parsing

Analyze the other methods of ReentrantLock.

2.2.1 the lock ()

public void lock(a) {
    sync.lock();
}
Copy the code

If the lock is not held by another thread, the lock is acquired immediately and the holding of the lock is counted +1. If the current thread has reached the lock, count+1 and return immediately. If the lock is held by another thread, the current thread is unavailable until the lock is acquired.

2.2.2 lockInterruptibly ()

public void lockInterruptibly(a) throws InterruptedException {
    sync.acquireInterruptibly(1);
}
Copy the code

To obtain a lock, there are the following scenarios:

  • If the lock is not acquired by another thread, it is immediately acquired and returned with +1 on the lock counter
  • If the current thread already holds the lock, return immediately and add +1 to the lock counter
  • If the lock is occupied by another thread, the current thread is unavailable until two things happen: (1) the current thread actively obtains the lock; ② Other threads call the current thread’s Interrupts method.

2.2.3 tryLock ()

public boolean tryLock(a) {
    return sync.nonfairTryAcquire(1);
}
Copy the code

Attempts to acquire the lock immediately if it is not held by another thread and returns true and the lock counter +1, or false otherwise. Although if lock is set to fair lock, a call to tryLock will request the lock immediately (if it is available). Regardless of whether another thread is in the wait state.

2.2.4 unlock ()

public void unlock(a) {
    sync.release(1);
}
Copy the code

Attempts to release the lock, and if the current thread holds the lock, the lock counter is reduced by -1. If the current counter is 0, the lock is released. If the current thread does not hold locks, throw IllegalMonitorStateException anomalies.

2.2.5 getHoldCount ()

public int getHoldCount(a) {
    return sync.getHoldCount();
}
Copy the code

Gets the number of lock counters that are locked on the “holding” thread.

2.2.6 isHeldByCurrentThread ()

public boolean isHeldByCurrentThread(a) {
    return sync.isHeldExclusively();
}
Copy the code

Whether the current thread holds the lock.

2.3 Differences between ReentrantLock and Synchronized

  1. ReenTrantLockYou can specify whether the lock is fair or unfair. whilesynchronizedCan only be an unfair lock. The so-called fair lock is that the line that waits first gets the lock first.
  2. ReenTrantLockProvided aConditionClass that implements a group wake-up call for threads that need to be woken up, rather than likesynchronizedEither wake up a random thread or wake up all threads.
  3. ReenTrantLockProvides a mechanism to interrupt threads waiting for locks throughlock.lockInterruptibly()To implement this mechanism.
  4. ReentrantLocksynchronizedCompared to theReentrantLockProvides more, more comprehensive functions, with stronger scalability. For example, time lock wait can be interrupted lock wait.

Refer to the reading

  1. ReenTrantLock a summary of ReenTrantLock
  2. Java Multithreading series – mutex ReentrantLock in “JUC Lock” 02