This series of articles are included in the public number [Ccww technology Blog], the original technical articles launched earlier than the blog
preface
ReentrantLock ReentrantLock ReentrantLock ReentrantLock ReentrantLock ReentrantLock ReentrantLock Some questions collected online:
- What is a reentrant lock?
- What are fair and unfair locks? What’s the difference?
- ReentrantLock::lock Fair lock mode reality
- How does ReentrantLock implement fair locking?
- How is ReentrantLock reentrant?
- ReentrantLock What is the difference between fair lock mode and unfair lock acquisition?
- ReentrantLock:: UNLOCK () Releases the lock. How do I wake up a thread in a waiting queue?
- What other features does ReentrantLock have besides ReentrantLock?
- ReentrantLock versus Synchrionized
- ReentrantLock usage scenarios
So what is a reentrant lock? What’s the use
What is ReentrantLock?
ReentrantLock is a typical exclusive mode AQS, where a synchronization state of 0 indicates idle. When a thread gets an idle synchronization state, it increments the synchronization state by one, making the synchronization state non-idle, and the other threads hang and wait. At the same time of modifying the synchronization state, and record its own thread, as the basis of subsequent re-entry, that is, when a thread holds the lock of an object, it can successfully acquire the lock of the object again. If it is a non-reentrant lock, it will cause a deadlock.
ReentrantLock involves fair and unfair locks. The key to the implementation of ReentrantLock is the implementation of the member variable sync, which is the core of lock mutually exclusive synchronization.
// Fair and unfair lock variables
private final Sync sync;
/ / parent class
abstract static class Sync extends AbstractQueuedSynchronizer {}
// Fair lock subclass
static final class FairSync extends Sync {}
// An unfair lock subclass
static final class NonfairSync extends Sync {}
Copy the code
What are fair locks and unfair locks? What’s the difference?
What are fair locks and unfair locks? What’s the difference?
A fair lock means that when the lock is available, the thread that has waited the longest on the lock gains access to it, i.e., first-in, first-out. Non-fair lock randomly allocates the right of use, which is a preemption mechanism. It is a random lock, and it is not necessarily the first to get the lock.
ReentrantLock provides a constructor that can implement a fair or unfair lock:
public ReentrantLock(boolean fair) {
sync = fair ? new FairSync() : new NonfairSync();
}
Copy the code
Although fair lock is guaranteed in fairness, fair lock acquisition does not take into account the scheduling factor of operating system for threads and other factors, which will affect performance.
Although the unfair mode is relatively efficient, if there are enough threads applying for locks in the unfair mode, some threads may not be locked for a long time, which is the “hunger” problem of the unfair lock.
But most of the time we use unfair locks because they perform much better than fair locks. But fair locking prevents thread hunger, and can be useful in some cases.
Let’s look at the ReentrantLock fair lock implementation:
ReentrantLock::lock Fair lock mode implementation
The first step is to create a fair lock by passing true in the constructor function
ReentrantLock reentrantLock = new ReentrantLock(true);
Copy the code
Call lock() to acquire(1)
public void lock(a) {
/ / call the sync subclasses FairSync lock () method: already. The lock () FairSync.
sync.lock();
}
final void lock(a) {
// Call acquire() on AQS with the value 1
acquire(1);
}
Copy the code
Try to get the lock directly,
// AbstractQueuedSynchronizer.acquire()
public final void acquire(int arg) {
// Try to get the lock
// If you fail, queue up
if(! tryAcquire(arg) &&// Notice that addWaiter() is passed in exclusive mode
acquireQueued(addWaiter(Node.EXCLUSIVE), arg))
selfInterrupt();
}
Copy the code
Specific lock acquisition process
-
GetState () gets the synchronization state value and checks whether it is 0:
- 24. If the state variable has the value 0, it indicates that no one is currently holding the lock. The use of hasqueuedToraise () ensures that both new and queued threads use the lock sequentially. Set it to the ‘exclusiveOwnerThread’ variable so you can re-enter the lock later.
- If the ‘exclusiveOwnerThread’ specifies that the current thread already owns the lock, and you are trying to acquire the lock, you need to set the value of the state variable
state+1
// ReentrantLock.FairSync.tryAcquire()
protected final boolean tryAcquire(int acquires) {
final Thread current = Thread.currentThread();
int c = getState();
// The value of the status variable is 0, indicating that no thread is currently holding the lock
if (c == 0) {
Hasqueuedtoraise () ensures that both new and alreadyqueued threads use the lock sequentially
if(! hasQueuedPredecessors() && compareAndSetState(0, acquires)) {
// The current thread has acquired the lock and set this thread to the 'exclusiveOwnerThread' variable.
// the lock can be reentered later
setExclusiveOwnerThread(current);
return true; }}// It is called a reentrant lock because if the lock fails to be acquired, it is determined again if the current thread already holds the lock
else if (current == getExclusiveOwnerThread()) {
int nextc = c + acquires;
if (nextc < 0)
throw new Error("Maximum lock count exceeded");
// Set to state
// Because the current thread holds the lock, other threads will only update the CAS state from 0 to 1, which will not succeed
// So there is no competition, there is no need to use CAS to update
setState(nextc);
return true;
}
return false;
}
Copy the code
What happens if the fetch fails to join the queue? By means of spin, the thread in the queue continuously tries to acquire the lock, and the middle can be interrupted by the way of interrupt.
-
If the previous node of the current node is the head node, then it is your turn to acquire the lock and call tryAcquire() to try again
final boolean acquireQueued(final Node node, int arg) { boolean failed = true; try { boolean interrupted = false; / / spin for (;;) { // The previous node of the current node, final Node p = node.predecessor(); // If the previous node of the current node is the head node, then it is its turn to acquire the lock / / call already. FairSync. TryAcquire () method of acquiring a lock to try again if (p == head && tryAcquire(arg)) { setHead(node); p.next = null; // help GC / / not failure failed = false; return interrupted; } // Whether to block if (shouldParkAfterFailedAcquire(p, node) && parkAndCheckInterrupt()) interrupted = true; }}finally { if (failed) // If that fails, cancel the lock acquisitioncancelAcquire(node); }}Copy the code
-
The previous Node of the current Node is not the Head, but needs to determine whether to block, and find a safe point to suspend.
private static boolean shouldParkAfterFailedAcquire(Node pred, Node node) { // The wait state of the last node int ws = pred.waitStatus; // The wait state is SIGNAL, which returns true if (ws == Node.SIGNAL) return true; // The state of the previous node is greater than 0, and the state is cancelled if (ws > 0) { // Remove all the previous unlisted nodes from the list do { node.prev = pred = pred.prev; } while (pred.waitStatus > 0); pred.next = node; } else { // If the state of the previous Node is less than or equal to 0, set its state to wake-up compareAndSetWaitStatus(pred, ws, Node.SIGNAL); } return false; } Copy the code
After looking at the process of obtaining locks, do you know how ReentrantLock achieves fair locking? This is in the implementation of tryAcquire().
How does ReentrantLock implement fair locking?
The use of hasqueuedToraise () in the implementation of tryAcquire() guarantees the use of a lock on FIFO by threads in a FIFO that does not create a “hunger” problem,
protected final boolean tryAcquire(int acquires) {
final Thread current = Thread.currentThread();
int c = getState();
// The value of the status variable is 0, indicating that no thread is currently holding the lock
if (c == 0) {
Hasqueuedtoraise () ensures that both new and alreadyqueued threads use the lock sequentially
if(! hasQueuedPredecessors() && compareAndSetState(0, acquires)) { .... }... }}public final boolean hasQueuedPredecessors(a) {
Node t = tail;
Node h = head;
Node s;
returnh ! = t && ((s = h.next) ==null|| s.thread ! = Thread.currentThread()); }Copy the code
TryAcquire checks to see if there are still any precursors in the CLH queue, and if there are, to continue to wait, thus ensuring a first-come-first-served policy.
So how does ReentrantLock implement ReentrantLock? How did it reenter?
How is ReentrantLock reentrant?
After obtaining the lock, set an identifier variable to the current thread ‘exclusiveOwnerThread’. When the thread enters again, determine whether the variable ‘exclusiveOwnerThread’ is equal to the current thread.
protected final boolean tryAcquire(int acquires) {
// The value of the status variable is 0, indicating that no thread is currently holding the lock
if (c == 0) {
if(! hasQueuedPredecessors() && compareAndSetState(0, acquires)) {
// The current thread has acquired the lock and set this thread to the 'exclusiveOwnerThread' variable.
// the lock can be reentered later
setExclusiveOwnerThread(current);
return true; }}// It is called a reentrant lock because if the lock fails to be acquired, it is determined again if the current thread already holds the lock
else if(current == getExclusiveOwnerThread()) { ... }}Copy the code
When we look at the process of fair lock acquisition, we also know about unfair lock acquisition, so let’s look at it.
ReentrantLock What is the difference between fair lock mode and unfair lock acquisition?
In fact, the difference between unfair lock acquisition and lock acquisition mainly lies in:
-
The constructor passes false or null to create a fair lock for NonfairSync,true,
-
When acquiring an unfair lock, check the state state first and then execute AQcuire (1) directly, which can improve efficiency.
final void lock(a) { if (compareAndSetState(0.1)) // If the synchronization status is successfully changed, the current thread is set as an exclusive thread setExclusiveOwnerThread(Thread.currentThread()); else / / acquiring a lock acquire(1); } Copy the code
-
24. There is no HasqueuedToraise () in tryAcquire() that guarantees the sequential use of the lock by both new and alreadyqueued threads.
Everything else is similar. ReentrantLock:: UNLOCK () is easier to understand now that we understand acquiring locks.
ReentrantLock:: UNLOCK () Releases the lock. How do I wake up a thread in a waiting queue?
-
Release the lock held by the current thread
protected final boolean tryRelease(int releases) { // Calculate the state value after release int c = getState() - releases; // If the lock is not occupied by the current thread, an exception is thrown if(Thread.currentThread() ! = getExclusiveOwnerThread())throw new IllegalMonitorStateException(); boolean free = false; if (c == 0) { // If the lock is reentered for 0 times, the lock is released successfully free = true; // Clear the exclusive thread setExclusiveOwnerThread(null); } // Update the state value setState(c); return free; } Copy the code
-
If the release is successful, wake up the threads in the waiting queue and check whether the status of the header node is SIGNAL. If so, wake up the threads associated with the next node of the header node. If the release fails, return false to indicate that the unlock failed.
- Set waitStatus to 0,
- When the next node of the head node is not empty, it will wake up the node directly. If the node is empty, the end of the queue will start to traverse forward to find the last node that is not empty, and then wake up.
private void unparkSuccessor(Node node) {
int ws = node.waitStatus;
if (ws < 0)
compareAndSetWaitStatus(node, ws, 0);
Node s = node.next;// where s is the next node to the head node (which now holds the lock), which is expected to wake up
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); // Wake up the thread represented by s
}
Copy the code
In addition to ReentrantLock’s reentrant feature, what other features can ReentrantLock have?
What other features does ReentrantLock have besides ReentrantLock?
- Thread interrupts are supported by adding an interrupt flag to the thread
interrupted
There is no impact on the running thread, and it is up to the user to decide what to do with the interrupt flag. For example, if a lock is not acquired for 5 seconds, the wait is interrupted and the thread continues to do something else. - Timeout mechanism, in
ReetrantLock::tryLock(long timeout, TimeUnit unit)
Provides a timeout lock acquisition function. The semantics are true if the lock was acquired within the specified time, false if it was not. This mechanism prevents threads from waiting indefinitely for locks to be released.
ReentrantLock versus Synchrionized
- ReentrantLock supports wait interruptibility, which can interrupt waiting threads
- ReentrantLock enables fair locking
- ReentrantLock enables selective notification, that is, multiple Condition queues
ReentrantLock usage scenarios
- Scenario 1: If a lock is already performed, the lock is not repeated and is used to perform non-important tasks, such as deleting unnecessary temporary files, checking the availability of certain resources, and backing up data
- Scenario 2: If the operation is being performed, wait for a period of time. If the operation times out, the operation is not performed to prevent resource deadlock caused by improper resource processing
- Scenario 3: If the operation is already locked, wait for the operation to be locked one by one, mainly used for resource scrambling (e.g. file operation, synchronous message sending, stateful operation, etc.)
- Scenario 4: Interruptible lock is used to cancel operations that are being synchronized to prevent blocking caused by abnormal operations being occupied for a long time
Is everyone still ok? If you like, move your hands to show 💗, point a concern!! Thanks for your support! Welcome to pay attention to the public number [Ccww technology blog], original technical articles launched at the first time