MindMap Gallery Java concurrency lock tool
This is a mind map about Java concurrency lock tools. These concurrency lock tools can play an important role in Java's concurrent programming and help you write efficient and safe concurrent code.
Edited at 2024-01-18 10:28:15El cáncer de pulmón es un tumor maligno que se origina en la mucosa bronquial o las glándulas de los pulmones. Es uno de los tumores malignos con mayor morbilidad y mortalidad y mayor amenaza para la salud y la vida humana.
La diabetes es una enfermedad crónica con hiperglucemia como signo principal. Es causada principalmente por una disminución en la secreción de insulina causada por una disfunción de las células de los islotes pancreáticos, o porque el cuerpo es insensible a la acción de la insulina (es decir, resistencia a la insulina), o ambas cosas. la glucosa en la sangre es ineficaz para ser utilizada y almacenada.
El sistema digestivo es uno de los nueve sistemas principales del cuerpo humano y es el principal responsable de la ingesta, digestión, absorción y excreción de los alimentos. Consta de dos partes principales: el tracto digestivo y las glándulas digestivas.
El cáncer de pulmón es un tumor maligno que se origina en la mucosa bronquial o las glándulas de los pulmones. Es uno de los tumores malignos con mayor morbilidad y mortalidad y mayor amenaza para la salud y la vida humana.
La diabetes es una enfermedad crónica con hiperglucemia como signo principal. Es causada principalmente por una disminución en la secreción de insulina causada por una disfunción de las células de los islotes pancreáticos, o porque el cuerpo es insensible a la acción de la insulina (es decir, resistencia a la insulina), o ambas cosas. la glucosa en la sangre es ineficaz para ser utilizada y almacenada.
El sistema digestivo es uno de los nueve sistemas principales del cuerpo humano y es el principal responsable de la ingesta, digestión, absorción y excreción de los alimentos. Consta de dos partes principales: el tracto digestivo y las glándulas digestivas.
Java concurrency lock tool
AbstractQueueSynchronizer
Introduction
The basic framework for building locks or other synchronization components, mainly used to manage synchronization status
The main way to use it is inheritance
Blocking can only occur at one moment, reducing the cost of context switching.
inherit
AbstractOwnableSynchronizer
Basic implementation of synchronizer that allows threads to occupy exclusive mode
Member variables
private transient Thread exclusiveOwnerThread
The thread currently occupying the synchronization state
main method
protected final void setExclusiveOwnerThread(Thread thread)
Set exclusive thread
protected final Thread getExclusiveOwnerThread()
Get the current exclusive thread
implement interface
Serializable
inner class
static final class Node
Introduction
AQS internally uses a two-way FIFO synchronization queue to complete the management of synchronization status. When a thread fails to obtain, it is added to the end of the queue. The structure of the queue is Node; the head and tail nodes are defined in AQS. The condition queue also uses Node's definition, but is in another queue
Member variables
static constant
static final Node SHARED = new Node()
Indicates that the node is in shared mode
static final Node EXCLUSIVE = null
Indicates that the node is in exclusive mode and only one thread holds the synchronization state at the same time.
static final int CANCELLED = 1
The node (thread) is in this state due to timeout or interruption, and will not change to other states subsequently, nor should it be blocked anymore.
static final int SIGNAL = -1
When the node (thread) is in this state, the successor node is or will be in a blocked state (through park), so when the current node releases the lock or is canceled, unpark needs to be called to wake up the successor node.
static final int CONDITION = -2
Indicates that the current node (thread) is in the condition queue (via await) and is not part of the synchronization queue until the status is set to 0 at a certain moment (via signal)
static final int PROPAGATE = -3
Propagate shared locks (used by the head node)
volatile int waitStatus
Including the above CANCELLED/SIGNAL/CONDITION/PROPAGATE and 0 (indicating that it is not in these four states), a non-negative number indicates that the node does not require interaction
volatile Node prev
Precursor node
volatile Node next
successor node
volatile Thread thread
The thread corresponding to the node
Node nextWaiter
In the synchronization queue, it indicates whether the node is in a shared or exclusive state, equal to SHARED to indicate sharing, and null to indicate exclusive; in the conditional queue, it indicates a reference to the next node.
Condition can only be used when the synchronization queue is in exclusive mode, so this variable is designed to be shared.
main method
Construction method
Node()
Used to establish initial nodes or construct SHARED nodes
Node(Thread thread, Node mode)
For use in addWaiter
Node(Thread thread, int waitStatus)
Used in Condition
member method
final boolean isShared()
return nextWaiter == SHARED
final Node predecessor()
Get the predecessor node, throw an exception when it is null
public class ConditionObject
Introduction
The implementation of the Condition interface serves the basic Lock implementation. Use Node to construct a waiting queue. Each Condition object contains a FIFO queue (one-way)
The queue uses Node's nextWaiter property as a reference to the next node.
implement interface
Condition
Serializable
Member variables
static constant
private static final int REINTERRUPT = 1
Indicates the need to interrupt again when leaving wait
private static final int THROW_IE = -1
Indicates that InterruptedException needs to be thrown when leaving wait
private transient Node firstWaiter
Points to the head node of the conditional queue
private transient Node lastWaiter
Points to the tail node of the condition queue
main method
public final void await()
Make the calling thread enter the waiting queue, release the lock and enter the blocking state. After the node is awakened by signal/signalAll, try to acquire the lock. If the blocking is awakened by an interrupt, it will respond to the interrupt.
private Node addConditionWaiter()
Add a new node to the waiting queue.
Specific implementation logic: When it is found that lastWaiter is canceled, call unlinkCancelledWaiters to clear the canceled methods in the entire queue (both firstWaiter and lastWaiter may point to new nodes); then create a new node (CONDITION state) and add it to the end of the queue
private void unlinkCancelledWaiters()
Use while to clear nodes whose waitStatus is not CONDITION from firstWaiter onwards. Since the method call is before the lock is released, the loop process does not need to be locked. In order to avoid residue in GC, when there is no signal, this method is only used when timeout or cancellation occurs.
The waitStatus of the node in the condition queue should only have two states: CONDITION and CANCELLED.
final int fullyRelease(Node node)
Release the lock state. If the lock state fails to be released, an IllegalMonitorStateException will be thrown and the node will be set to the CANCELLED state. If successful, the state value before release will be returned.
release
The release method of AQS is called here, and a suitable node is found to wake up.
The parameters use the state attribute of AQS
final boolean isOnSyncQueue(Node node)
Determine whether the node is in the synchronization queue (note that this is a synchronization queue rather than a conditional queue): When the waitStatus is CONDITION, it means that it is not in the synchronization queue. If the pre or next of the node is null, it also means that it is not in the synchronization queue (these two attributes are used for the synchronization queue. The predecessor and successor of the conditional queue do not use these two attributes), and then call the findNodeFromTail method to traverse the synchronization queue from the tail node to see if the node is in it.
The relationship between synchronization queue and condition queue: lock competition only relies on synchronization queue. Condition queue only saves threads blocked due to lack of conditions. When threads need to compete for locks, they still need to be converted to synchronization queue.
This method is used as the judgment condition of while in the code. If the node is not in the synchronization queue (indicating that it is not signal/signalAll), it will enter the while and block through park. When the node is awakened, it will first determine whether it was awakened by an interrupt. If not, it will continue to return to the while process. Otherwise, it will try to add the node to the synchronization queue and break out of the while process.
acquireQueued(node, savedState)
Constantly trying to obtain the savedState synchronization status for the node nodes in the synchronization queue
unlinkCancelledWaiters
After acquiring the lock, if node.nextWaiter is not null, call this method to clear the node in the canceled state.
reportInterruptAfterWait
If it was awakened due to an interrupt before, the interrupt needs to be processed based on the results of the previous judgment, whether to reset the interrupt status or throw an interrupt exception.
public final long awaitNanos(long nanosTimeout)
The basic logic is similar to await, with the addition of timeout judgment.
I don’t quite understand the latter part of transferAfterCancelledWait.
public final boolean await(long time, TimeUnit unit)
The basic logic is similar to awaitNanos, you can specify the time unit, and ultimately it is converted to nanoseconds for calculation.
public final void awaitUninterruptibly()
The basic logic is similar to await and does not respond to interrupts (that is, selfInterrupt resets the interrupt status)
public final boolean awaitUntil(Date deadline)
The basic logic is similar to await, with the addition of a maximum blocking time.
public final void signal()
Move the longest waiting thread from the conditional queue to the synchronization queue
protected boolean isHeldExclusively()
Returns whether the current thread holds the lock in exclusive mode, if so, returns true. This method is in AQS and does not provide a specific implementation. However, since this method is only used in condition, it does not need to be implemented if ConditionObject is not used, otherwise it needs to be implemented.
private void doSignal(Node first)
Remove the nodes from the condition queue and convert them into nodes from the synchronization queue. The implementation process is a loop, from front to back, until it encounters a node that is not null and not in the CANCELLED state, convert it and exit the loop
final boolean transferForSignal(Node node)
Convert the node from the conditional queue to the synchronization queue and return whether the conversion is successful. The implementation logic is to first determine whether the node status is CONDITION. If it is not a canceled node, return false; then call enq to enter the synchronization queue and obtain the predecessor node of the node in the queue. After that, if the status of the predecessor node is > 0 or CAS is modified If waitStatus fails (changes have occurred), unpark the thread and finally return true
It does not necessarily wake up directly after joining the synchronization queue. Only when waitStatus>0 or changes during CAS execution will the thread be unparked and awakened. If you do not wake up at this time, you will wait until the synchronization queue logic and then wake up.
enq
public final void signalAll()
Transfer all qualified (not canceled) nodes in the condition queue to the synchronization queue. The basic logic is similar to signal, the difference is that the transferForSignal method is executed in a loop
isHeldExclusively
private void doSignalAll(Node first)
transferForSignal
protected final boolean hasWaiters()
Whether there are threads in the condition queue, the implementation method is to cycle the condition queue from the beginning, if there is a node whose status is CONDITION, it returns true, otherwise it returns false
Member variables
static variable
static final long spinForTimeoutThreshold = 1000L;
The threshold used for timeout. If it is less than this value, there is no need to call park with timeout, but let the loop continue to execute. At this time, the spin efficiency is higher
private static final Unsafe unsafe
Unsafe.getUnsafe(), the following are all used to obtain the offset through the objectFieldOffset method, which facilitates subsequent modifications directly through Unsafe's CAS operation.
private static final long stateOffset
AQS state
private static final long headOffset
AQS head
private static final long tailOffset
AQS tail
private static final long waitStatusOffset
Node's waitStatus
private static final long nextOffset
Node next
private transient volatile Node head
The head node of the synchronization queue, lazy loading, only modified through setHead; when the head node exists, its status cannot be CANCELLED
private transient volatile Node tail
The tail node of the synchronization queue, lazy loading
private volatile int state
Sync status
main method
protected final int getState()
Returns the current value of synchronization status
protected final void setState(int newState)
Set current sync status
protected final boolean compareAndSetState(int expect, int update)
CAS sets state, and the bottom layer calls Unsafe's CAS method.
private Node addWaiter(Node mode)
enqueue method
Add the current thread to the end of the queue using the specified mode
It is worth noting that
The parameter Node is used to specify the mode, and the thread is obtained through currentThread
In the implementation, when the tail is not empty, quickly try cas to set the tail. If successful, it will return directly. If it fails, the enq method will be called.
private Node enq(final Node node)
Loop (spin) CAS. When tail is empty, CAS initializes head (new node) first. When tail is not empty, CAS sets tail.
The parameter node here is the node that needs to be added rather than the mode.
public final boolean hasQueuedPredecessors()
Determine whether there is a node before the current thread, if so, return true, otherwise return false. Used for the implementation of fair locks. Specific implementation: When the queue is not empty, it returns true if it is judged that the successor node of head is empty or not the current thread.
When is head.next null?
public final boolean hasQueuedThreads()
return head != tail; Whether there are threads waiting in the queue
public final boolean isQueued(Thread thread)
Whether the thread is in the queue, the implementation is to loop from the end to the beginning
public final int getQueueLength()
Get the queue length. The implementation method is to loop from the end to the beginning to calculate the number of nodes whose thread is not null.
public final Collection<Thread> getQueuedThreads()
Gets the collection of threads in the queue and returns it in the form of Collection. The implementation is to construct an ArrayList, loop from end to beginning to add threads that are not null, and then return the ArrayList object.
public final boolean owns(ConditionObject condition)
Whether the condition object belongs to the current synchronizer AQS
exclusive mode
Only one thread can obtain synchronization status at the same time
public final void acquire(int arg)
Exclusive mode obtains the synchronization status and ignores interruptions; first call tryAcquire to try to acquire the exclusive lock, and after failure, call acquireQueued(addWaiter(Node.EXCLUSIVE), arg) to try to add the thread to the synchronization queue and determine the node status, and then based on the returned result (whether there is (Thread interrupt) Call the thread interrupt method to restore the interrupt state
acquire itself does not respond to interrupts, so the processing of interrupts is to restore the interrupt status
protected boolean tryAcquire(int arg)
Try to obtain an exclusive lock, return true if successful, false if failed. The specific implementation is completed by subclasses, and thread safety needs to be ensured when obtaining synchronization status.
final boolean acquireQueued(final Node node, int arg)
arg can be understood as a resource that needs to be competed. AQS is represented internally by state, but in fact the specific use is defined by the developer.
Continuously try to acquire locks for threads already in the queue (node has been created by addWaiter)
Implementation logic: Spin as follows: If the predecessor node is head and acquires the lock successfully, set the current node to head and the return is not interrupted; otherwise, proceed to the following operations: call shouldParkAfterFailedAcquire to determine and set the node status and call parkAndCheckInterrupt to block the thread and Check interrupt status. If an exception occurs during the process, call cancelAcquire to cancel the acquisition.
In fact, even if the lock is not acquired, the loop will not stop. When the status of the predecessor node is successfully set and the thread is interrupted with park, the thread is suspended; The node after head will use tryAcquire to compete with the new thread for the lock, indicating that the implementation of the lock here is unfair.
private void setHead(Node node)
Set the node to head, and set the thread and prev attributes to null to facilitate GC. In fact, it is equivalent to dequeuing from the head of the queue.
private static boolean shouldParkAfterFailedAcquire(Node pred, Node node)
Check and update the status of the predecessor node of the node that failed to acquire the lock to SIGNAL, and return whether the current thread needs to be blocked.
Implementation logic: Determine the waitStatus of the predecessor node, and return true when it is SIGNAL; when >0, it means that the predecessor node has been cancelled, and the predecessor node will be skipped in a loop until the waitStatus of a predecessor node<=0, and then return false; otherwise CAS Set the waitStatus of the predecessor node to SIGNAL and return false
private final boolean parkAndCheckInterrupt()
Call park, check the interrupt status and return
return thread.interrupted() The result returned here is used to determine whether the thread was awakened due to an interrupt. If it was awakened due to an interrupt, it will still be in the loop in acquireQueued and will be blocked by park again (because the interrupted() method will clear the interrupt flag , so park can take effect again); when awakened by unpark, the lock will be acquired; when pseudo-awakened, just check again through the outer loop
What causes false arousal?
private void cancelAcquire(Node node)
Called when the entire method throws an exception, empty the thread from the Node, set the status to CANCELLED, and find the node with waitStatus<=0 as the precursor; remove yourself from the queue, and wake up the following one if necessary. a suitable node
private void unparkSuccessor(Node node)
It should be noted that if the next of the node is not empty, unpark it; if the next of the node is null, start from the tail and find the frontmost node with waitStatus<=0 that is not the node from the tail to the front. unpark
The reason why we search from back to front is because setting the predecessor and successor of a doubly linked list is not an atomic operation. It may happen that the next of a node is empty but is already in the queue, or the node has just been canceled and its next points to itself. , and the traversal just comes to this node
public final void acquireInterruptibly(int arg)
To obtain synchronization status in exclusive mode in response to interruption, Thread.interrupted() will be used to determine whether it is interrupted before tryAcqure. If so, an exception will be thrown.
tryAcquire
try it once first
private void doAcquireInterruptibly(int arg)
The basic logic is the same as acquireQueued. The difference is that addWaiter is called internally and does not return whether there is a thread interruption, but directly throws an InterruptedException after checking that the park is awakened by the interruption.
cancelAcquire
public final boolean tryAcquireNanos(int arg, long nanosTimeout)
Exclusive acquisition of synchronization status with timeout, responding to interrupts. If the synchronization status is not obtained within the timeout period, false is returned.
tryAcquire
try it once first
private boolean doAcquireNanos(int arg, long nanosTimeout)
The basic logic is the same as doAcquireInterruptibly, with the addition of time judgment logic and the use of spinForTimeoutThreshold threshold.
cancelAcquire
public final boolean release(int arg)
Exclusive mode releases synchronization state. First, tryRelease is called to try to modify the state. After success, it is judged that head != null and head.waitStatus!=0 (the waitStatus of head here is 0, indicating an empty queue). When the waitStatus of head is 0, it means an empty queue. When it is called, unparkSuccessor is called to wake up the subsequent nodes, and then returns true; tryRelease fails and returns false.
protected boolean tryRelease(int arg)
Try to modify the state. There is no specific implementation in AQS and subclasses need to implement it themselves. The arg variable of the release method is used for this method; the return value is whether the state is successfully released
For a description of arg, see the implementation section below.
unparkSuccessor
Sharing mode
Multiple threads obtain synchronization status at the same time
public final void acquireShared(int arg)
Shared mode acquires synchronization status and ignores interrupts. Implementation: First call tryAcquireShared to try to obtain the synchronization status. After failure (the result is less than 0), call doAcquireShared to spin to obtain the synchronization status.
protected int tryAcquireShared(int arg)
Try to obtain the synchronization status. No specific method is provided in AQS and is implemented by subclasses. A return value less than 0 indicates that the acquisition failed; equal to 0 indicates that the acquisition was successful, but no other shared mode acquisition was successful; greater than 0 indicates that the acquisition was successful and other sharing mode acquisitions may also be successful.
private void doAcquireShared(int arg)
After joining the queue, spin tries to obtain the synchronization status. The basic logic is basically the same as acquireQueued in exclusive mode. The difference is that when the predecessor of the node is head, the result returned by tryAcquireShared needs to be judged. If it is greater than or equal to 0 (indicating that there are still resources, can continue to propagate), then call setHeadAndPropagate to set the head and propagation attributes (set a new head and judge the successor node, and wake it up if appropriate); otherwise, you still need to call shouldParkAfterFailedAcquire and parkAndCheckInterrupt for subsequent operations (park and subsequent interrupt judgment, etc.). In addition, the selfTninterrupt method for setting the interrupt status is also executed in this method.
tryAcquireShared
When the predecessor of the node is head, try it first.
private void setHeadAndPropagate(Node node, int propagate)
The embodiment of communication. What this method actually checks is the successor node of the node that currently acquires the lock, that is, it can be propagated backward once. At the beginning, I was wondering why I didn't find any code similar to waking up backwards in a loop. Later I found that setHeadAndPropagate itself is in for(;;). Each awakened node will execute this method if it acquires the lock, so This achieves the purpose of backward propagation one by one. Note that they are not awakened together and then compete, they are awakened one by one.
Continue to wake up nodes backward. This method will be called only when tryAcquireShared returns the result r>=0 (indicating that there are still resources available), and r is passed into the method as a propagate parameter. This method will set the node to head, and then determine whether it can be passed backwards. If so, call doReleaseShared to wake up the subsequent node.
Judgment logic: propagate>0 (indicating that there is permission) Or previous head == null Or previous head.waitStatus < 0 Or the current head (that is, node) == null Or now head.waitStatus < 0 Then go to the next step: node.next == null or node.next.isShared()
Actually it’s not very clear: 1. Why do we need to determine the current head ((h = head) == null) 2. Why doReleaseShared is still required after node.next == null
1's speculation: Because head may be modified by other threads during the judgment process, if the new head still meets this condition, you can still continue; I feel that the judgment logic here is more like I don't know why head is null, but I still try it. A conservative processing strategy, then 2 may be a similar conservative strategy
It is only judged that waitStatus<0 is because the head status may change to SIGNAL, and CONDITION will not appear here.
private void doReleaseShared()
Spin wakes up the successor node, which is different from the independent mode in that it also needs to ensure the propagation properties. Implementation: Loop: When the queue is not empty, determine the waitStatus of head. When it is equal to SIGNAL, CAS sets waitStatus to 0. If successful, unparkSuccessor(head) will be called to wake up the successor node. If it fails, start over from the beginning; waitStatus is 0 and CAS sets waitStatus to 0. When PROPAGATE fails, start over; then judge head==h to see if the head node has been modified, and if so, exit the loop.
Note that this method does not pass parameters and starts directly from head.
Speculation: The shared mode node may only have three states: 0 (just created), PROPAGATE and SIGNAL.
unparkSuccessor
cancelAcquire
public final void acquireSharedInterruptibly(int arg)
Shared mode obtains synchronization status and supports interrupts in a manner similar to exclusive mode.
tryAcquireShared
private void doAcquireSharedInterruptibly(int arg)
Similar to doAcquireShared logic, an exception is thrown after an interruption is detected
cancelAcquire
public final boolean tryAcquireSharedNanos(int arg, long nanosTimeout)
Shared mode with timeout period obtains synchronization status and supports interruption.
tryAcquireShared
private boolean doAcquireSharedNanos(int arg, long nanosTimeout)
The timeout judgment logic is similar to the exclusive mode, and other execution logic is similar to doAcquireShared
cancelAcquire
public final boolean releaseShared(int arg)
Shared mode releases synchronization state
protected boolean tryReleaseShared(int arg)
Empty method, implemented by subclasses, attempts to release synchronization state. arg is the parameter of releaseShared and can be defined by yourself; the return value is true when this method successfully releases the synchronization state and other threads can acquire, otherwise it returns false
doReleaseShared
Here we still only wake up a subsequent node, and the subsequent wake-up propagation is carried out by the awakened node.
How to implement your own lock based on AQS
competing resources
AQS uses the private variable state to abstractly represent the resources for lock competition (for example, 1 means occupied, 0 means not occupied)
Operations on state require the use of relevant methods: getState, setState, compareAndSetState, etc.
On-demand implementation method
tryAcquire, tryAcquireShared, tryRelease, tryReleaseShared, isHeldExclusively
The parameter arg of various methods in AQS is ultimately passed into these methods, so these methods determine whether the arg parameter is required and how to use it. This is the meaning of "can represent anything" in the source code comments.
Exclusive lock (independent mode)
protected boolean tryAcquire(int arg);
Implementing queries and obtaining competitive resources is usually completed by the CAS operation of the state. The incoming parameters can be defined by the developer.
Simple example: protected boolean tryAcquire(int acquires) { assert acquires == 1; // Define that the incoming parameter can only be 1 if (compareAndSetState(0, 1)) { return true; } return false; } protected boolean tryRelease(int releases) { assert releases == 1; // Define that the incoming parameter can only be 1 if (getState() == 0) throw new IllegalMonitorStateException(); setState(0); return true; }
protected boolean tryRelease(int arg);
To release competing resources, usually the state variable is decremented or cleared to zero. The incoming parameters can be defined by the developer.
There should be no blocking or waiting inside the method
shared lock
protected int tryAcquireShared(int acquires)
The incoming parameters are defined by the developer and can be regarded as the number of resources they want to obtain; the return value int can be regarded as the remaining number of shared resources.
Simple example: protected int tryAcquireShared(int acquires) {//non-fair for (;;) { int available = getState(); int remaining = available - acquires; if (remaining < 0 || compareAndSetState(available, remaining)) return remaining; } } protected boolean tryReleaseShared(int releases) { for (;;) { int current = getState(); int next = current releases; if (compareAndSetState(current, next)) return true; } }
protected boolean tryReleaseShared(int releases)
The incoming parameters are defined by developers themselves and can be regarded as the number of resources they want to release.
condition variable
Although the implementation logic of AQS does not use state, when using condition variables, state will be used as the incoming parameter of release (int arg) by default, so it is often necessary to modify the state variable when implementing these methods.
protected boolean isHeldExclusively()
Returns whether the current thread exclusively occupies the mutex lock corresponding to the condition variable
Common implementation methods: protected boolean isHeldExclusively() { return getExclusiveOwnerThread() == Thread.currentThread(); } protected boolean tryAcquire(int acquires) { ... setExclusiveOwnerThread(Thread.currentThread()); return true; }
ReentrantReadWriteLock
ReentrantLock
Introduction
Reentrant locks are divided into two methods: fair locks and unfair locks.
implement interface
Lock
Serializable
inner class
abstract static class Sync
Abstract class is the basis for the implementation of ReetrantLock
inherit
AbstractQueuedSynchronizer
main method
abstract void lock()
Is it an abstract method to acquire a lock? Does it need to be implemented by a subclass?
final boolean nonfairTryAcquire(int acquires)
Unfair tryLock implementation The specific implementation is: CAS acquires the lock when no thread currently acquires the lock. If successful, the lock-exclusive thread is set to the current thread; if the current thread already holds the lock, the number of current locks is updated. In these two cases, true will be returned, indicating that the lock is held, and false will be returned in other cases.
So why would an unfair implementation be placed in the parent class Sync? Because this method is used by both fair locks and unfair locks, the tryLocks of both are based on this implementation.
protected final boolean tryRelease(int releases)
The implementation of tryRelease in AQS is to first calculate the total number of locks minus the number of released locks to get the number of remaining held locks c. If c is 0, clear the state; set the state to the value of c and return whether no locks are held.
protected final boolean isHeldExclusively()
isHeldExclusively implemented in AQS, return getExclusiveOwnerThread() == Thread.currentThread()
final int getHoldCount()
Returns how many locks the current thread holds. This method relies on the judgment of isHeldExclusively
final boolean isLocked()
return getState() != 0; Whether a thread holds the lock
final Thread getOwner()
Returns the thread holding the lock, or null if not
final ConditionObject newCondition()
newConditionObject()
private void readObject(java.io.ObjectInputStream s)
Deserialization method, set state to 0
static final class FairSync
Fair lock implementation class
inherit
Sync
main method
final void lock()
acquire(1)
Parameter 1 here has practical significance, which is different from CountDownLatch.
protected final boolean tryAcquire(int acquires)
TryAcquire implementation of fair lock. The implementation process is: when the occupied lock c is 0, if there is no thread in front and cas successfully sets c to acquires, then set the current thread as an exclusive thread and return true; if the current thread already holds the lock, update the number of locks and return true; Returns false in all other cases
hasQueuedPredecessors
There is more judgment here than unfair lock. Even if no thread currently acquires the lock, if there is a thread waiting for a longer time, it will give up the acquisition of the lock.
static final class NonfairSync
Unfair lock implementation class
inherit
Sync
main method
final void lock()
First try direct cas(0,1). If it succeeds, it means that no one else is currently acquiring the lock. The current thread successfully grabbed the lock, and then set the exclusive thread as the current thread; if it fails, acquire(1)
In fact, the tryAcquire method of the unfair lock will also try cas. The direct cas here may be to highlight the unfairness. Earlier calls may obtain resources earlier.
protected final boolean tryAcquire(int acquires)
return nonfairTryAcquire(acquires) The unfair implementation already exists in Sync, call it directly
Member variables
private final sync
Indicates whether the lock is implemented fairly or unfairly, and corresponds to FairSync or NonfairSync after instantiation.
Constructor
public ReentrantLock()
Unfair lock constructed by default
public ReentrantLock(boolean fair)
Construct fair lock or unfair lock according to fair choice
main method
public void lock()
sync.lock()
public void lockInterruptibly()
sync.acquireInterruptibly(1)
public boolean tryLock()
return sync.nonfairTryAcquire(1); If the lock is successfully acquired or the current thread already holds the lock, it returns true, otherwise it returns false
No matter what the lock is here, it is an unfair attempt to acquire the lock.
public boolean tryLock(long timeout, TimeUnit unit)
return sync.tryAcquireNanos(1, unit.toNanos(timeout))
tryAcquireNano calls tryAcquire, so there is a difference between fairness and unfairness.
public void unlock()
sync.release(1)
public Condition newCondition()
return sync.newCondition()
public int getHoldCount()
return sync.getHoldCount() The number of locks held by the current thread
public boolean isHeldByCurrentThread()
return sync.isHeldExclusively() whether the current thread holds the lock
public boolean isLocked()
return sync.isLocked()
public final boolean isFair()
return sync instanceof FairSync
protected Thread getOwner()
return sync.getOwner()
public final boolean hasQueuedThreads()
return sync.hasQueuedThreads() whether there are threads in the queue
public final boolean hasQueuedThread(Thread thread)
return sync.isQueued(thread); Whether the current thread is in the queue
public final int getQueueLength()
return sync.getQueueLength();
protected Collection<Thread> getQueuedThreads()
return sync.getQueuedThreads();
public boolean hasWaiters(Condition condition)
Whether there are threads waiting on the given condition (corresponding to whether there are threads in CONDITION state on the condition queue)
public int getWaitQueueLength(Condition condition)
The number of threads in the CONDITION state in the condition queue for a given condition
protected Collection<Thread> getWaitingThreads(Condition condition)
Condition
Introduction
Interface; condition variables are mainly used to manage the dependence of thread execution on certain states, map the Object's monitor methods (wait/notify/notifyAll) to direct objects, and combine them with the use of any Lock implementation to achieve each An object has the effect of multiple wait sets. It is equivalent to Lock replacing synchronize, and Condition replacing the monitor method, that is, Condition needs to be used in conjunction with Lock.
The main differences between Condition and monitor methods are: 1. Condition supports multiple waiting queues, and one Lock instance can be bound to multiple Conditions. 2. Condition can support response interrupt and timeout settings
The deep meaning of binding multiple Conditions is that threads can be managed according to different situations. The so-called "fineer" control means that threads blocked due to different conditions are in different condition queues and will only be awakened when they need to be awakened. Entering the synchronization queue to compete, itself differentiates and isolates
understand
The difference from general lock competition is that condition emphasizes conditions. In a multi-threaded environment, conditions need to be met before certain operations can be performed; while general lock competition is just equivalent to competing for the right to execute a certain piece of code.
Usual programming model: Get lock; while (conditional state is not satisfied) { release lock; The thread hangs and waits until the condition is met for notification; Reacquire the lock; } Critical section operations; release lock;
Ternary relationship: lock, wait, conditional assertion (predicate). Each wait call is implicitly associated with a specific conditional assertion; when calling wait for a specific conditional assertion, the caller must already hold the lock of the conditional queue related to the assertion, and this lock must protect the conditions that constitute the conditional assertion. State variables
predicate: a function that returns bool type
create
Created through the newCondition method of the Lock interface, the class that needs to be implemented implements its own logic (essentially creating an instance of ConditionObject)
main method
void await()
Let the thread wait until it is awakened by signal or interrupt; when called, the lock associated with condition will be automatically released, and the thread will stop executing the current task until it is awakened by signal/signalAll/interrupt/spurious wakeup. The thread needs to retry to acquire the lock associated with the condition before calling the await method.
boolean await(long time, TimeUnit unit)
Similar to awaitNanos, you can specify the unit of time, and return false when the time is exhausted, otherwise return true
long awaitNanos(long nanosTimeout)
Similar to await, but will wake up after the specified time at the latest and return unused time
void awaitUninterruptibly();
Similar to await, but does not respond to interrupts
boolean awaitUntil(Date deadline)
Similar to awaitNanos, the way to specify the time is different. If the deadline has been reached, it returns false, otherwise it returns true.
void signal()
Wake up a thread in waiting state. After the thread wakes up, it needs to acquire the lock again.
void signalAll()
Wake up all waiting threads
Lock
Introduction
Interface provides some common methods of requesting/releasing locks
Comparison with synchronized
The related mechanisms of synchronized (such as monitor objects, etc.) are built-in and cannot be accessed at the language level. Lock is an alternative solution at the language level, so you can get some language-level conveniences (such as knowing whether the lock is successfully acquired, etc.)
The locking and unlocking process of synchronized is completed by the keyword itself, while Lock needs to explicitly call the locking and unlocking method.
Lock can use condition variables to use locks more flexibly
Lock can respond to interrupts, but synchronized cannot
main method
void lock()
void lockInterruptibly()
Interrupts will be responded to during the lock request process.
boolean tryLock()
Try to acquire the lock and return the result directly (without blocking the current thread)
boolean tryLock(long time, TimeUnit unit)
tryLock with timeout and response interrupt
void unlock();
Condition newCondition()
Returns a Condition object associated with the lock
LockSupport
Introduction
Basic thread blocking primitives (related to permits) used to create locks and other synchronization classes
The bottom layer relies on Unsafe implementation
Construction method
Private, construction of objects of this class is not allowed
Member variables
private static final sun.misc.Unsafe UNSAFE
Build in static code block
sun.misc.Unsafe
private static final long parkBlockerOffset
Obtain the offset position of the parkBlocker attribute in the Thread class object through Unsafe
parkBlocker is a record when the thread is blocked, which facilitates monitoring and diagnostic tools to identify the reason why the thread is blocked; it is null when the thread is not blocked.
private static final long SEED
Obtain the offset position of the threadLocalRandomSeed property in the Thread class through Unsafe
private static final long PROBE
Obtain the offset position of the threadLocalRandomProbe property in the Thread class through Unsafe
private static final long SECONDARY
Obtain the offset position of the threadLocalRandomSecondarySeed property in the Thread class through Unsafe
These three properties are set by ThreadLocalRandom
main method
public static Object getBlocker(Thread t)
Get the parkBlocker object in the Thread object through Unsafe
private static void setBlocker(Thread t, Object arg)
Private method, set the parkBlocker object in Thread through Unsafe, the following methods of setting blocker call this method
public static void park()
Block the thread and wait for "permit" to continue execution.
Specific implementation (Hotspot): Parker class
具体的实现与平台相关,每个线程都有一个Parker实例,在linux实现中实际上是以mutex和condition最终实现了锁和阻塞的过程(具体看笔记文章,这里不写了)
主要成员变量(linux实现)
private volatile int _counter
表示许可,大于0表示已获取许可,每次park只会将其设置为1,不会递增
Therefore, calling unpark twice and then calling park twice will still block the second time.
protected pthread_mutex_t _mutex[1]
互斥变量
在代码中可以通过linux原子指令pthread_mutex_lock、pthread_mutex_trylock、pthread_mutex_unlock对变量进行加锁和解锁操作
protected pthread_cond_t _cond[1]
条件变量
利用线程间共享的全局变量进行同步的一种机制,一般和互斥锁结合使用(避免条件变量的竞争);可以通过pthread_cond_wait、pthread_cond_timedwait等指令进行操作
There is also a waiting queue in the Linux kernel to manage blocked processes
方法
park
unpark
Differences in use from wait
wait needs to wait for notify before it can wake up. There is a sequential process, and the unpark that park is waiting for can be called before park. At this time, the program after park can continue to execute.
wait needs to get the monitor of the object, but park does not
Notice
If an interruption occurs in park, park will also return but will not throw an interruption exception.
public static void park(Object blocker)
Due to the role of parkBlocker, it is more recommended to use the park method with blocker parameters. After using it, you can see the prompt on which object the thread is blocked on through diagnostic tools such as jstack, such as parking to wait for <0x000000045ed12298> (a xxxx object all qualified name)
public static void parkNanos(Object blocker, long nanos)
public static void parkNanos(long nanos)
The maximum blocking time before the program continues execution
public static void parkUntil(Object blocker, long deadline)
public static void parkUntil(long deadline)
public static void unpark(Thread thread)