[TOC]
Refer to the link: www.bilibili.com/video/BV12K…
With CAS, we can implement optimistic lock operation, which causes threads to synchronize, but through the CAS source, we find that CAS can only modify a value in memory, but not to synchronize objects, so how to synchronize objects? At the same time, with multiple threads competing for a unified resource, how can you manage all the threads that need that resource? Thus, AQS was born.
Reference: In Java Virtual Machine
AbstractQueuedSynchronizer abstract synchronous queue AQS for short, it is the foundation of the realization of synchronizer components, and in the process of contract awarding the bottom of the lock is achieved by AQS. Structure is as follows
attribute
int state
In shared mode, you need to indicate the number of threads holding the shared lock.
Shared and exclusive locks (exclusive locks)
Shared lock: This lock can be held by multiple threads. The shared lock only supports read data. If a thread holds a shared lock on data, other data can only hold the shared lock on the data.
Exclusive lock: Only one thread can acquire the lock.
Shared and exclusive locks are different implementations of AQS
Node head & Node tail
A bidirectional linked list for maintaining a FIFO, with two nodes pointing to the head Node and the tail Node
Node
The structure of the nodes in the queue is shown in the figure above
Methods (take exclusive mode as an example)
tryAcquire(int arg)
protected boolean tryAcquire(int arg) {
throw new UnsupportedOperationException();
}
Copy the code
Attempts to obtain the lock. The system returns if obtaining the lock fails.
This method simply throws an exception that the AQS inheritance class needs to inherit to give the upper level open space for users to write business logic.
acquire(int arg)
public final void acquire(int arg) {
if(! tryAcquire(arg) && acquireQueued(addWaiter(Node.EXCLUSIVE), arg)) selfInterrupt(); }Copy the code
After the tryAcquire method fails, it enters the wait queue
addWaiter(Node.EXCLUSIVE), arg)
It creates a Node and inserts the Node into the waiting queue.
private Node addWaiter(Node mode) {
Node node = new Node(Thread.currentThread(), mode);
// Get the current tail node. Tail is an attribute of AQS
Node pred = tail;
if(pred ! =null) {
node.prev = pred;
// Try atomic operations to set the current node to the tail node
// Tail can be modified by other threads after pred is obtained
// compareAndSetTail(Node expect, Node update) reads the offset of tail
// Determine whether the current pred is still the end of the queue (which may be modified by other threads), and if so, update the end of the queue to the current node
if (compareAndSetTail(pred, node)) {
pred.next = node;
returnnode; }}// Call the full join method if the above attempt to join the team quickly fails
// For example, tail is modified
enq(node);
return node;
}
Copy the code
acquireQueued(final Node node, int arg)
After joining the queue, the lock is acquired by spin in the queue.
As can be seen from the code, the nodes after the head node form a wait queue
When the first node after the head gains the lock, the node becomes the new head node
final boolean acquireQueued(final Node node, int arg) {
boolean failed = true;
try {
boolean interrupted = false;
// Optional operation
for (;;) {
final Node p = node.predecessor();
The current thread succeeded in obtaining the lock
if (p == head && tryAcquire(arg)) {
// The current node serves as the head node
setHead(node);
p.next = null; // help GC
failed = false;
return interrupted;
}
// Determine whether the thread needs to be suspended based on the current node state
if (shouldParkAfterFailedAcquire(p, node) &&
parkAndCheckInterrupt())
interrupted = true; }}finally {
if(failed) cancelAcquire(node); }}Copy the code
boolean tryRelease(int arg)
Same as tryAcquire, as open to upper level methods
protected boolean tryRelease(int arg) {
throw new UnsupportedOperationException();
}
Copy the code
boolean release(int arg)
The lock is released and the queue is notified to change the state of the thread in the waiting queue
public final boolean release(int arg) {
if (tryRelease(arg)) {
Node h = head;
if(h ! =null&& h.waitStatus ! =0)
// Pass in the head and wake up the node in the waiting queue
unparkSuccessor(h);
return true;
}
return false;
}
Copy the code
unparkSuccessor(Node node)
The head node notifies the nodes in the waiting queue when the resource is finished
In the following code, why doesn’t the wake up start at the beginning node
The search is not atomic, and searches from back to front may be because the queue is not built in order
- The back node pre points to the front node
- The former node next points to the latter node
From front to back, the search may be interrupted because step 2 has not been completed
private void unparkSuccessor(Node node) {
// Set the state of the head node
int ws = node.waitStatus;
if (ws < 0)
compareAndSetWaitStatus(node, ws, 0);
Node s = node.next;
if (s == null || s.waitStatus > 0) {
s = null;
// Start the search from the tail node, the first node after head and the node whose waitStatus <= 0
for(Node t = tail; t ! =null&& t ! = node; t = t.prev)if (t.waitStatus <= 0)
s = t;
}
// Wake up the found node, and then spin the acquire method to acquire the lock
if(s ! =null)
LockSupport.unpark(s.thread);
}
Copy the code
Sharing model
In shared mode, locks can be acquired by multiple threads and are represented by an increase in the state value.
After the thread completes the lock operation, it releases the lock and the state decreases.
Lock acquisition:
After the lock that uses the lock resource is released
- Exclusive mode: Only the first node is awakened
- Shared mode: Wakes up all nodes in the queue that are suspended in shared mode