How to use ReentrantLock
ReentrantLock is usually used as a member variable of a class for locking and releasing locks. Here is an example
java
copy code
class X { private final ReentrantLock lock = new ReentrantLock(); // ... public void m() { lock.lock(); // block until condition holds try { // ... method body } finally { lock.unlock(); } } }
ReentrantLock
Generally used as a member variable, lock
the lock is acquired through the method. If the lock is not acquired, it will be blocked. After the lock is acquired, the business logic is executed and finally
the lock is released in the code to prevent deadlock.
ReentrantLock constructor
Let's first look at ReentrantLock
the constructor of the class
java
copy code
public class ReentrantLock implements Lock, java.io.Serializable { private static final long serialVersionUID = 7373984872572414699L; /** Synchronizer providing all implementation mechanics */ private final Sync sync; // 省略部分代码 /** * Creates an instance of {@code ReentrantLock}. * This is equivalent to using {@code ReentrantLock(false)}. */ public ReentrantLock() { sync = new NonfairSync(); } /** * Creates an instance of {@code ReentrantLock} with the * given fairness policy. * * @param fair {@code true} if this lock should use a fair ordering policy */ public ReentrantLock(boolean fair) { sync = fair ? new FairSync() : new NonfairSync(); } // 省略部分代码 }
ReentrantLock provides two construction methods. The default construction method implements an unfair lock internally NonfairSync
. You can also specify whether to create a fair lock FairSync
or an unfair lock through parameters NonfairSync
.
static inner class Sync
Sync
Let's first look at the structure of the static inner class
java
copy code
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 { 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(); /** * 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() { // 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() { return new ConditionObject(); } // Methods relayed from outer class final Thread getOwner() { return getState() == 0 ? null : getExclusiveOwnerThread(); } final int getHoldCount() { return isHeldExclusively() ? getState() : 0; } final boolean isLocked() { return getState() != 0; } /** * Reconstitutes the instance from a stream (that is, deserializes it). */ private void readObject(java.io.ObjectInputStream s) throws java.io.IOException, ClassNotFoundException { s.defaultReadObject(); setState(0); // reset to unlocked state } } // 省略部分代码 }
Sync
FairSync
It is an abstract class that provides two implementations by default, which are the fair lock and unfair lock we mentioned above NonfairSync
. The fair lock and unfair lock acquire locks in different ways. The fair lock will first determine whether there are other threads in the waiting queue In the queue, if there is, it will join the tail of the waiting queue, if not, it will try to CAS
acquire the lock, if the lock cannot be obtained, it will join the tail of the waiting queue. When the non-fair lock acquires the lock, it directly CAS
tries to acquire the lock, regardless of whether there are other threads in the waiting queue.
Sync
Inheritance AbstractQueuedSynchronizer
is AbstractQueuedSynchronizer
what we often call AQS
classes.
AQS (AbstractQueuedSynchronizer)
Inside is a int
status field state
and a FIFO waiting queue (doubly linked list), which state
is equal to 0 means that no thread holds the lock, state
and greater than 0 means that a thread holds the lock.
Implementation of ReentrantLock#lock unfair lock
Let's ReentrantLock#lock
follow up the code step by step through the method
java
copy code
public class ReentrantLock implements Lock, java.io.Serializable { private static final long serialVersionUID = 7373984872572414699L; /** Synchronizer providing all implementation mechanics */ private final Sync sync; // 省略部分代码 /** * Acquires the lock. * * <p>Acquires the lock if it is not held by another thread and returns * immediately, setting the lock hold count to one. * * <p>If the current thread already holds the lock then the hold * count is incremented by one and the method returns immediately. * * <p>If the lock is held by another thread then the * current thread becomes disabled for thread scheduling * purposes and lies dormant until the lock has been acquired, * at which time the lock hold count is set to one. */ public void lock() { sync.lock(); } // 省略部分代码 }
We can see that a method of a subclass lock
is called inside the method . Let's first look at the implementation of unfair locks Sync
lock
NonfairSync
java
copy code
public class ReentrantLock implements Lock, java.io.Serializable { private static final long serialVersionUID = 7373984872572414699L; /** Synchronizer providing all implementation mechanics */ private final Sync sync; // 省略部分代码 /** * 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() { if (compareAndSetState(0, 1)) setExclusiveOwnerThread(Thread.currentThread()); else acquire(1); } protected final boolean tryAcquire(int acquires) { return nonfairTryAcquire(acquires); } } // 省略部分代码 public void lock() { sync.lock(); } // 省略部分代码 }
The unfair lock first compareAndSetState
tries to change state
the value of the value from 0 to 1 through the method. If the modification is successful, the current thread is set as the thread holding the lock.
java
copy code
final void lock() { if (compareAndSetState(0, 1)) setExclusiveOwnerThread(Thread.currentThread()); else acquire(1); }
AQS
No, the method of the parent class is being executed acquire
, we continue to follow up the method AbstractQueuedSynchronizer
in the class acquire
java
copy code
/** * Acquires in exclusive mode, ignoring interrupts. Implemented * by invoking at least once {@link #tryAcquire}, * returning on success. Otherwise the thread is queued, possibly * repeatedly blocking and unblocking, invoking {@link * #tryAcquire} until success. This method can be used * to implement method {@link Lock#lock}. * * @param arg the acquire argument. This value is conveyed to * {@link #tryAcquire} but is otherwise uninterpreted and * can represent anything you like. */ public final void acquire(int arg) { if (!tryAcquire(arg) && acquireQueued(addWaiter(Node.EXCLUSIVE), arg)) selfInterrupt(); }
First call tryAcquire
the method, you can see that the method AbstractQueuedSynchronizer
in the class tryAcquire
is a protected
modified method, and an exception is thrown directly inside, then it is obvious that this method needs to be implemented by a subclass (template method mode)
java
copy code
/** * Attempts to acquire in exclusive mode. This method should query * if the state of the object permits it to be acquired in the * exclusive mode, and if so to acquire it. * * <p>This method is always invoked by the thread performing * acquire. If this method reports failure, the acquire method * may queue the thread, if it is not already queued, until it is * signalled by a release from some other thread. This can be used * to implement method {@link Lock#tryLock()}. * * <p>The default * implementation throws {@link UnsupportedOperationException}. * * @param arg the acquire argument. This value is always the one * passed to an acquire method, or is the value saved on entry * to a condition wait. The value is otherwise uninterpreted * and can represent anything you like. * @return {@code true} if successful. Upon success, this object has * been acquired. * @throws IllegalMonitorStateException if acquiring would place this * synchronizer in an illegal state. This exception must be * thrown in a consistent fashion for synchronization to work * correctly. * @throws UnsupportedOperationException if exclusive mode is not supported */ protected boolean tryAcquire(int arg) { throw new UnsupportedOperationException(); }
Then we go to NonfairSync
the class and see that tryAcquire
the method calls the method Sync
of the parent classnonfairTryAcquire
java
copy code
/** * 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)) { // 通过 CAS 去抢锁 // 抢到锁了,抢持有锁的线程 exclusiveOwnerThread 设置为当前线程 setExclusiveOwnerThread(current); return true; } } // 当前已经有线程获取到了锁,判断持有锁到线程是不是自己 else if (current == getExclusiveOwnerThread()) { // 持有锁的线程是自己(可重入锁) int nextc = c + acquires; if (nextc < 0) // overflow throw new Error("Maximum lock count exceeded"); // 将 state 值+1 setState(nextc); return true; } // 没有获取到锁,返回 false return false; }
So far, the implementation AQS
in the class tryAcquire
has been analyzed. If the lock is not acquired, the conditional judgment in the if will continue to be executed, and we continue to analyze the AQS
class addWaiter
method
java
copy code
public final void acquire(int arg) { if (!tryAcquire(arg) && acquireQueued(addWaiter(Node.EXCLUSIVE), arg)) selfInterrupt(); }
addWaiter
Method implementation, if the lock is not acquired, the current thread needs to be added to the waiting queue
java
copy code
/** * Creates and enqueues node for current thread and given mode. * * @param mode Node.EXCLUSIVE for exclusive, Node.SHARED for shared * @return the new node */ private Node addWaiter(Node mode) { // 将当前线程封装到 Node 实例 Node node = new Node(Thread.currentThread(), mode); // Try the fast path of enq; backup to full enq on failure Node pred = tail; // 链表尾指针 if (pred != null) { // 新node.prev 指向前面node node.prev = pred; if (compareAndSetTail(pred, node)) { // 通过 CAS 将 tail 指向新加入的 node pred.next = node; // 将前面node.next 指向新node return node; } } // 尾指针为 null 的情况 enq(node); return node; }
AQS
compareAndSetTail
method in class
java
copy code
/** * CAS tail field. Used only by enq. */ private final boolean compareAndSetTail(Node expect, Node update) { return unsafe.compareAndSwapObject(this, tailOffset, expect, update); }
AQS
enq
method in class
java
copy code
/** * Inserts node into queue, initializing if necessary. See picture above. * @param node the node to insert * @return node's predecessor */ private Node enq(final Node node) { for (;;) { // for 循环,通过 CAS 将新node加入到链表尾部 Node t = tail; if (t == null) { // Must initialize if (compareAndSetHead(new Node())) // 通过 CAS 初始化 head 结点 tail = head; // 将 tail 指向 head } else { // 以下逻辑与 addWaiter 方法上面实现一致 node.prev = t; if (compareAndSetTail(t, node)) { t.next = node; return t; } } } }
So far, the thread that has not acquired the lock has been successfully added to the end of the waiting queue, and AQS
the class addWaiter
method will return the node newly added to the waiting queue
java
copy code
public final void acquire(int arg) { if (!tryAcquire(arg) && acquireQueued(addWaiter(Node.EXCLUSIVE), arg)) selfInterrupt(); }
We continue to analyze acquireQueued
the method
java
copy code
/** * Acquires in exclusive uninterruptible mode for thread already in * queue. Used by condition wait methods as well as acquire. * * @param node the node * @param arg the acquire argument * @return {@code true} if interrupted while waiting */ final boolean acquireQueued(final Node node, int arg) { boolean failed = true; try { boolean interrupted = false; for (;;) { // 自旋 final Node p = node.predecessor(); // 新加入node的前一个结点 // p == head 说明新加入的node是链表中的第一个数据结点,尝试获取锁 if (p == head && tryAcquire(arg)) { // 获取锁成功,将链表中的第一个结点设置为头结点 setHead(node); p.next = null; // help GC failed = false; return interrupted; } if (shouldParkAfterFailedAcquire(p, node) && parkAndCheckInterrupt()) interrupted = true; } } finally { if (failed) cancelAcquire(node); } }
Through the for loop spin, get the previous node of the current node, if the previous node is the head node, then try to acquire the lock, if the lock is acquired successfully. The head node of the update queue is the current node. Before the head points to an empty Node node (new Node()), here the thread and prev of the current node node are set to null, and the head points to the current node.
java
copy code
/** * Sets head of queue to be node, thus dequeuing. Called only by * acquire methods. Also nulls out unused fields for sake of GC * and to suppress unnecessary signals and traversals. * * @param node the node */ private void setHead(Node node) { head = node; // 将当前结点设置为头结点 node.thread = null; node.prev = null; }
If the node newly added to the waiting queue is not the first node, or is the first node but fails to acquire the lock, the method will be executed. This shouldParkAfterFailedAcquire
method is used to determine whether the current thread needs to park after failing to acquire the lock ( hang).
java
copy code
/** * Checks and updates status for a node that failed to acquire. * Returns true if thread should block. This is the main signal * control in all acquire loops. Requires that pred == node.prev. * * @param pred node's predecessor holding status * @param node the node * @return {@code true} if thread should block */ private static boolean shouldParkAfterFailedAcquire(Node pred, Node node) { int ws = pred.waitStatus; if (ws == Node.SIGNAL) /* * This node has already set status asking a release * to signal it, so it can safely park. */ return true; if (ws > 0) { /* * Predecessor was cancelled. Skip over predecessors and * indicate retry. */ do { node.prev = pred = pred.prev; } while (pred.waitStatus > 0); pred.next = node; } else { /* * waitStatus must be 0 or PROPAGATE. Indicate that we * need a signal, but don't park yet. Caller will need to * retry to make sure it cannot acquire before parking. */ compareAndSetWaitStatus(pred, ws, Node.SIGNAL); } return false; }
If the current thread needs to be suspended, execute parkAndCheckInterrupt
the method
java
copy code
/** * Convenience method to park and then check if interrupted * * @return {@code true} if interrupted */ private final boolean parkAndCheckInterrupt() { LockSupport.park(this); return Thread.interrupted(); }
Implementation of ReentrantLock#unlock unfair lock
java
copy code
/** * Attempts to release this lock. * * <p>If the current thread is the holder of this lock then the hold * count is decremented. If the hold count is now zero then the lock * is released. If the current thread is not the holder of this * lock then {@link IllegalMonitorStateException} is thrown. * * @throws IllegalMonitorStateException if the current thread does not * hold this lock */ public void unlock() { sync.release(1); }
AQS
the release
method called
java
copy code
/** * Releases in exclusive mode. Implemented by unblocking one or * more threads if {@link #tryRelease} returns true. * This method can be used to implement method {@link Lock#unlock}. * * @param arg the release argument. This value is conveyed to * {@link #tryRelease} but is otherwise uninterpreted and * can represent anything you like. * @return the value returned from {@link #tryRelease} */ public final boolean release(int arg) { if (tryRelease(arg)) { // 释放锁成功,获取头结点 Node h = head; if (h != null && h.waitStatus != 0) // 唤醒等待队列中的其他线程 unparkSuccessor(h); return true; } return false; }
AQS
The called method tries to release the lock. The method in tryRelease
the same method has not been implemented specifically. This method needs to be implemented by subclasses. We enter the class to view the specific implementationAQS
tryRelease
Sync
java
copy code
protected final boolean tryRelease(int releases) { int c = getState() - releases; if (Thread.currentThread() != getExclusiveOwnerThread()) // 判断持有锁的线程是否是当前线程,如果不是抛异常 throw new IllegalMonitorStateException(); boolean free = false; if (c == 0) { // 如果state 的值为0,表示锁释放,对于可重入锁,加锁和解锁要成对出现 free = true; // 将持有锁的线程设置为 null setExclusiveOwnerThread(null); } // 更新 state 的值为0 setState(c); return free; }
Release the lock successfully, obtain the head node of the waiting queue, and wake up other threads in the waiting queue
java
copy code
/** * Wakes up node's successor, if one exists. * * @param node the node */ private void unparkSuccessor(Node node) { /* * If status is negative (i.e., possibly needing signal) try * to clear in anticipation of signalling. It is OK if this * fails or if status is changed by waiting thread. */ int ws = node.waitStatus; if (ws < 0) compareAndSetWaitStatus(node, ws, 0); /* * Thread to unpark is held in successor, which is normally * just the next node. But if cancelled or apparently null, * traverse backwards from tail to find the actual * non-cancelled successor. */ Node s = node.next; if (s == null || s.waitStatus > 0) { s = null; for (Node t = tail; t != null && t != node; t = t.prev) if (t.waitStatus <= 0) s = t; } if (s != null) LockSupport.unpark(s.thread); }
This article mainly introduces the underlying implementation principle of ReentrantLock unfair lock. You can check the source code for the implementation of fair lock.