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

  1. Deadknock Java Synchronization series write a Lock Lock yourself

  2. Unsafe parsing of Java magic classes

  3. JMM (Java Memory Model)

  4. Volatile parsing of the Java Synchronization series

  5. 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.