“This is the 15th day of my participation in the November Gwen Challenge. Check out the event details: The Last Gwen Challenge 2021


All the source code for this blog is JDK 1.8

ReentrantLock, a ReentrantLock, is a recursive non-blocking synchronization mechanism. It can be equivalent to the use of synchronized, but ReentrantLock provides a more powerful and flexible locking mechanism than synchronized, reducing the probability of deadlocks occurring. The API is described as follows:

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 also provides the option of a fair or unfair lock. The constructor accepts an optional fair argument (default unfair lock) that, when set to true, indicates a fair lock and otherwise indicates an unfair lock. The difference between a fair lock and an unfair lock is that the lock acquisition of a fair lock is sequential. But fair locks tend to be less efficient than non-fair locks, and fair locks show lower throughput in the case of many threads accessing.

Acquiring a lock

We typically use ReentrantLock to acquire locks like this:

ReentrantLock lock = new ReentrantLock(); lock.lock();Copy the code

Methods the lock:

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

Sync is already inside of an inner class, it inherits AQS (AbstractQueuedSynchronizer), it has two subclasses: fair lock FairSync NonfairSync and non fair lock.

Sync implements nonfairTryAcquire(int Acquires) by default. ReentrantLock implements nonfairTryAcquire(int acquires) by default. You can see that this is the default implementation of unfair locking. Let’s look at the unfair lock() method:

If (compareAndSetState(0, 1)) setExclusiveOwnerThread(thread.currentThread ()); AQS acquire(int arg) acquire(1); }Copy the code

The first attempt to acquire the lock quickly is made, and if it fails, the acquire(int arg) method is called, which is defined in AQS as follows:

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

TryAcquire (int arg) = tryAcquire(int arg) = tryAcquire(int arg)

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 (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

The main logic of this method is: first, determine the synchronization state state == 0? If yes, the lock is not held by any thread. CAS is used to obtain the synchronization status. If the state! If = 0, it determines whether the current thread is the thread that acquired the lock. If so, it acquires the lock and returns true on success. The thread that successfully acquired the lock acquires the lock again, which increases the synchronization state.

Release the lock

ReentrantLock provides unlock to release the lock:

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

Unlock uses Sync’s internal release(int arg) as defined in AQS:

public final boolean release(int arg) { if (tryRelease(arg)) { Node h = head; if (h ! = null && h.waitStatus ! = 0) unparkSuccessor(h); return true; } return false; }Copy the code

TryRelease (int arg), acquire(int arg), tryRelease(int arg), acquire(int arg)

Protected final Boolean tryRelease(int releases) {// Subtract 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; }Copy the code

This method returns true only when the synchronization state is completely freed. When state == 0, the thread holding the lock is set to null and free= true, indicating that the lock is released successfully.

Fair and unfair locks

The difference between a fair lock and an unfair lock is whether the locks are acquired in FIFO order. TryAcquire (int arg) = tryAcquire(int arg) = tryAcquire(int ARg)

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

A comparison of the process used by an unfair lock and a fair lock to acquire the synchronous state reveals that the only difference between the two is that the fair lock obtains the synchronous state with an additional constraint: hasqueued24 (), defined as follows:

public final boolean hasQueuedPredecessors() { Node t = tail; // Node h = head; // head Node s; // head node! Return h! = t && ((s = h.next) == null || s.thread ! = Thread.currentThread()); }Copy the code

This method does one thing: it basically determines whether the current thread is the first in the CLH synchronization queue. Returns true if it is, false otherwise.

The difference between ReentrantLock and synchronized

As mentioned earlier, ReentrantLock provides a more flexible and powerful locking mechanism than synchronized. What makes ReentrantLock so flexible and powerful? What are the differences between them?

First, they must have the same functionality and memory semantics.

  1. Compared with synchronized, ReentrantLock provides more, more comprehensive functions and has stronger scalability. For example: time lock wait, can interrupt lock wait, lock vote.
  2. ReentrantLock also provides conditional conditions, which are more detailed and flexible about thread wait and wake operations, so ReentrantLock is more suitable in places where there are multiple Condition variables and highly contested locks (conditions will be discussed later).
  3. ReentrantLock provides pollable lock requests. It attempts to acquire the lock and continues if it succeeds, otherwise it can wait until the next run time, whereas synchronized either succeeds or blocks once the lock is entered, so ReentrantLock is less likely to cause deadlocks than synchronized.
  4. ReentrantLock supports a more flexible synchronized block of code, but with synchronized, it can only be retrieved and released in the same synchronized block structure. Note: Lock release for ReentrantLock must be handled in finally, otherwise serious consequences may occur.
  5. ReentrantLock supports interrupt handling and performs better than synchronized.

Recommended reading

Acquiring a lock, there are many ways in the process of releasing the lock are combined using the method of AQS, as the basis of synchronous components, AQS do too much work, custom synchronization method to realize the custom component simply, then add AQS provide template method, can realize powerful custom synchronous components, so read the following four blog, ReentrantLock is a piece of cake to understand.

  1. AQS at —–J.U.C: An introduction to AQS
  2. AQS of —–J.U.C: CLH synchronization queues
  3. AQS of —–J.U.C: Obtaining and releasing synchronization state
  4. AQS of —–J.U.C: Blocking and waking up threads

The resources

  1. Doug Lea: Java Concurrent Programming in Action
  2. Fang Tengfei: The Art of Java Concurrent Programming