The problem
(1) What is AQS?
(2) Positioning of AQS?
(3) The realization principle of AQS?
(4) Based on AQS to achieve their own lock?
Introduction to the
Is the full name of AQS AbstractQueuedSynchronizer, its location is for almost all the locks in Java and synchronizer provides a basic framework.
AQS is based on FIFO queue implementation, and internal maintenance of a state variable state, through atomic update of the state variable state can be unlocked operation.
The content of this chapter and the following chapters may be difficult to understand, so it is recommended to read tong Brother’s previous chapter “Write a Lock yourself” in the Java Synchronization series.
The core source
Main inner class
static final class Node {
// Indicates that a node is in shared mode
static final Node SHARED = new Node();
// Indicates that a node is mutually exclusive
static final Node EXCLUSIVE = null;
// Indicates that the thread has been canceled
static final int CANCELLED = 1;
// Indicates that the successor node needs to be woken up
static final int SIGNAL = -1;
// Indicates that threads are waiting on a condition
static final int CONDITION = -2;
// indicate that the following shared lock needs to be propagated unconditionally (shared lock needs to wake up the reading thread continuously)
static final int PROPAGATE = -3;
// The wait state corresponding to the thread saved by the current node
volatile int waitStatus;
// The previous node
volatile Node prev;
// The last node
volatile Node next;
// The thread saved by the current node
volatile Thread thread;
// The next node waiting on the Condition (used when Condition locks)
Node nextWaiter;
// Whether the mode is shared
final boolean isShared(a) {
return nextWaiter == SHARED;
}
// Get the previous node
final Node predecessor(a) throws NullPointerException {
Node p = prev;
if (p == null)
throw new NullPointerException();
else
return p;
}
// Node constructor
Node() { // Used to establish initial head or SHARED marker
}
// Node constructor
Node(Thread thread, Node mode) { // Used by addWaiter
// Shared mode or mutually exclusive mode is stored in the nextWaiter field
this.nextWaiter = mode;
this.thread = thread;
}
// Node constructor
Node(Thread thread, int waitStatus) { // Used by Condition
// The waiting state, used in Condition
this.waitStatus = waitStatus;
this.thread = thread; }}Copy the code
In a typical double-linked list structure, nodes store information about the current thread, the previous node, the next node, and the state of the thread.
The main properties
// The head node of the queue
private transient volatile Node head;
// The last node of the queue
private transient volatile Node tail;
// Control the lock unlock state variable
private volatile int state;
Copy the code
Defines a state variable to control lock unlocking and a queue to place waiting threads.
Note that these variables need to be volatile because they are operating in a multithreaded environment and their changes need to be immediately visible to other threads.
These changes are handled directly using the broadening class:
// Get an instance of the Unsafe class. Note that this is for JDK use only, and not for ordinary users
private static final Unsafe unsafe = Unsafe.getUnsafe();
// The offset of the state variable state
private static final long stateOffset;
// The offset of the head node
private static final long headOffset;
// The offset of the tail node
private static final long tailOffset;
// Wait state offset (Node property)
private static final long waitStatusOffset;
// The offset of the next Node (Node attributes)
private static final long nextOffset;
static {
try {
// Get the offset of state
stateOffset = unsafe.objectFieldOffset
(AbstractQueuedSynchronizer.class.getDeclaredField("state"));
// Get the offset of head
headOffset = unsafe.objectFieldOffset
(AbstractQueuedSynchronizer.class.getDeclaredField("head"));
// Get the offset of tail
tailOffset = unsafe.objectFieldOffset
(AbstractQueuedSynchronizer.class.getDeclaredField("tail"));
// Get the offset of waitStatus
waitStatusOffset = unsafe.objectFieldOffset
(Node.class.getDeclaredField("waitStatus"));
// Get the offset of next
nextOffset = unsafe.objectFieldOffset
(Node.class.getDeclaredField("next"));
} catch (Exception ex) { throw newError(ex); }}// Call the Unsafe method atom to update state
protected final boolean compareAndSetState(int expect, int update) {
return unsafe.compareAndSwapInt(this, stateOffset, expect, update);
}
Copy the code
For more information about the Unsafe class, refer to tong Ge’s previous post on parsing parsing parsing the Unsafe Java magic class.
The main methods that subclasses need to implement
We can see is the full name of AQS AbstractQueuedSynchronizer, it is essentially an abstract class, it is in essence should be need a subclass to implement, so subclasses implement a synchronizer need to implement what method?
// Used in mutually exclusive mode: try to obtain the lock
protected boolean tryAcquire(int arg) {
throw new UnsupportedOperationException();
}
// Used in exclusive mode: attempt to release the lock
protected boolean tryRelease(int arg) {
throw new UnsupportedOperationException();
}
// In shared mode: try to obtain the lock
protected int tryAcquireShared(int arg) {
throw new UnsupportedOperationException();
}
// In shared mode: try to release the lock
protected boolean tryReleaseShared(int arg) {
throw new UnsupportedOperationException();
}
// Returns true if the current thread owns the lock
protected boolean isHeldExclusively(a) {
throw new UnsupportedOperationException();
}
Copy the code
Question: Why don’t these methods be defined as abstract methods?
Because subclasses only need to implement some of these methods to implement a synchronizer, there is no need to define an abstract method.
Here we introduce some methods in AQS through a case study.
Write a lock yourself based on AQS
Directly on the code:
public class MyLockBaseOnAqs {
// Define a synchronizer that implements the AQS class
private static class Sync extends AbstractQueuedSynchronizer {
// Implement tryAcquire(acquires)
@Override
public boolean tryAcquire(int acquires) {
if (compareAndSetState(0.1)) {
setExclusiveOwnerThread(Thread.currentThread());
return true;
}
return false;
}
Implement the tryRelease(Releases) method
@Override
protected boolean tryRelease(int releases) {
setExclusiveOwnerThread(null);
setState(0);
return true; }}// Declare the synchronizer
private final Sync sync = new Sync();
/ / lock
public void lock(a) {
sync.acquire(1);
}
/ / unlock
public void unlock(a) {
sync.release(1);
}
private static int count = 0;
public static void main(String[] args) throws InterruptedException {
MyLockBaseOnAqs lock = new MyLockBaseOnAqs();
CountDownLatch countDownLatch = new CountDownLatch(1000);
IntStream.range(0.1000).forEach(i -> new Thread(() -> {
lock.lock();
try {
IntStream.range(0.10000).forEach(j -> {
count++;
});
} finally {
lock.unlock();
}
// System.out.println(Thread.currentThread().getName());
countDownLatch.countDown();
}, "tt-"+ i).start()); countDownLatch.await(); System.out.println(count); }}Copy the code
Running the main() method always prints 10 million (10 million), indicating that the lock is also usable directly, but it is also a non-reentrant lock.
Is it very simple, just need to simply realize the two methods of AQS to complete the previous chapter tong elder brother himself to achieve the lock function.
How does it work?
We will not cover the source code in this chapter, but we will understand it after learning ReentrantLock.
conclusion
This chapter was over, we didn’t go to in-depth analysis of AQS source code, the author thinks that it is not necessary, because I have never seen the lock related source of students, started telling AQS source will certainly be a face of meng force, specific source, we in the back of the lock and synchronizer to learn, And so on all with AQS related source code learning finished, and then a summary.
Here is a summary of the main content of this chapter:
(1) AQS is a basic framework for almost all locks and synchronizers in Java, and I say “almost” here, because there are very few that do not implement AQS;
(2) AQS maintains a queue, which uses a double linked list to save the thread waiting for the lock queue;
(3) AQS maintains a state variable, control this state variable can realize the lock unlock operation;
(4) Based on AQS to write a lock is very simple, only need to achieve several methods of AQS.
eggs
In fact, the lock written by Tong Ge himself in the last chapter can be regarded as an epitome of AQS. Basically, you can understand half of AQS after understanding it, because Tong Ge did not write the content related to Condition in that chapter. In the next chapter, we will study the content related to Condition together in ReentrantLock.
So, I suggest you check out this article and click on the recommended reading below.
Recommended reading
-
Deadknock Java Synchronization series write a Lock Lock yourself
-
Unsafe parsing of Java magic classes
-
JMM (Java Memory Model)
-
Volatile parsing of the Java Synchronization series
-
Synchronized parsing of the Java synchronization series
Welcome to pay attention to my public number “Tong Elder brother read source code”, view more source code series articles, with Tong elder brother tour the ocean of source code.