Small knowledge, big challenge! This article is participating in the creation activity of “Essential Tips for Programmers”.

ReentrantLock: ReentrantLock: ReentrantLock: ReentrantLock: ReentrantLock: ReentrantLock: ReentrantLock: ReentrantLock: ReentrantLock: ReentrantLock: ReentrantLock: ReentrantLock: ReentrantLock Have you ever wondered why simply calling lock() and unlock() can solve thread safety problems?

CAS

As we all know, Java also has a way of thread synchronization, synchronized keyword, which can solve the problem of thread safety. However, because synchronized is realized through the operating system Mutex Lock, the efficiency of synchronized is relatively low. It’s called a heavyweight lock. Fortunately, JDK1.6 officially changed synchronized in a more in-depth way, introducing biased locking, lightweight locking, lock elimination, lock coarsening and other mechanisms, which greatly improved the performance of synchronized.

Before JDK1.6, in order to solve the problem of low synchronized performance, Doug Lea developed Java and distributed a large number of components, which made a great contribution to the development of Java. It implemented a variety of clever mechanisms to ensure thread safety without locking, such as:

public class LockDemo {

    private AtomicReference<Thread> atomicReference = new AtomicReference<>();

    public void lock(a) {
        // Get the current thread object
        Thread thread = Thread.currentThread();
        // Spin wait
        while(! atomicReference.compareAndSet(null, thread)) {
        }
    }

    public void unlock(a) {
        // Get the current thread object
        Thread thread = Thread.currentThread();
        atomicReference.compareAndSet(thread, null);
    }

    static int count = 0;

    public static void main(String[] args) throws InterruptedException {
        LockDemo lockDemo = new LockDemo();
        List<Thread> threadList = new ArrayList<>();
        for (int i = 0; i < 50; i++) {
            Thread thread = new Thread(() -> {
                lockDemo.lock();
                for (int j = 0; j < 1000; j++) {
                    count++;
                }
                lockDemo.unlock();
            });
            thread.start();
            threadList.add(thread);
        }
        // Wait for the thread to complete
        for(Thread thread : threadList) { thread.join(); } System.out.println(count); }}Copy the code

The program uses CAS mechanism to implement a spin lock, to ensure thread safety, Java and a large number of packages using CAS.

AQS

Moving on to the topic of this article, AQS, let’s take a ReentrantLock program as an example:

public class LockDemo {

    private static int count = 0;

    public static void main(String[] args) throws InterruptedException {
        List<Thread> threadList = new ArrayList<>();
        Lock lock = new ReentrantLock();
        for (int i = 0; i < 50; ++i) {
            Thread thread = new Thread(() -> {
                lock.lock();
                try {
                    for (int j = 0; j < 1000; j++) { count++; }}finally{ lock.unlock(); }}); thread.start(); threadList.add(thread); }for(Thread thread : threadList) { thread.join(); } System.out.println(count); }}Copy the code

When we create a ReentrantLock object:

public ReentrantLock(a) {
    sync = new NonfairSync();
}
Copy the code

The NonfairSync object is created and assigned to sync. What is sync?

private final Sync sync;
Copy the code

It is a variable of type Sync, which is an inner class of ReentrantLock:

abstract static class Sync extends AbstractQueuedSynchronizer {... }Copy the code

Sync inherited from AbstractQueuedSynchronizer, it is our key to introduce AQS, meaning abstract queue synchronizer. So what we’re actually creating is an abstract queue synchronizer.

A thread executes the lock() method.

public void lock(a) {
    sync.lock();
}
Copy the code

Sync’s lock() method is called:

static final class NonfairSync extends Sync {
    private static final long serialVersionUID = 7316153563782823691L;

    /** * Performs lock. Try immediate barge, backing up to normal * acquire on failure. */
    final void lock(a) {
        if (compareAndSetState(0.1))
            setExclusiveOwnerThread(Thread.currentThread());
        else
            acquire(1);
    }

    protected final boolean tryAcquire(int acquires) {
        returnnonfairTryAcquire(acquires); }}Copy the code

This is an inner class of ReentrantLock that inherits from Sync, so execute its Lock method, in which CAS is used, first execute the compareAndSetState() method, since neither NonfairSync nor Sync classes override this method, So it performs the AbstractQueuedSynchronizer class:

protected final boolean compareAndSetState(int expect, int update) {
    // See below for intrinsics setup to support this
    return unsafe.compareAndSwapInt(this, stateOffset, expect, update);
}
Copy the code

The meaning of this method is to predict AbstractQueuedSynchronizer class attributes in the state value is 0, if it is 0, it is updated to 1, for the first thread, it must be established, so the modification is successful, return true value, and continue to perform the if block in the method:

setExclusiveOwnerThread(Thread.currentThread());
Copy the code

It is still performed AbstractQueuedSynchronizer methods in:

protected final void setExclusiveOwnerThread(Thread thread) {
    exclusiveOwnerThread = thread;
}
Copy the code

It sets the current owner of the exclusive mode synchronization, i.e., whichever thread has state set to 1, which means it owns it, makes that thread the owner of the resource, where the lock() method ends.

At this point, if a second thread wants to grab the resource, it executes the lock() method, also going to this method:

final void lock(a) {
    if (compareAndSetState(0.1))
        setExclusiveOwnerThread(Thread.currentThread());
    else
        acquire(1);
}
Copy the code

At this point, the thread expects state to be 0, but state has already been changed to 1 by the first thread. The second thread must fail to update and return false, so acquire() :

public final void acquire(int arg) {
    if(! tryAcquire(arg) && acquireQueued(addWaiter(Node.EXCLUSIVE), arg)) selfInterrupt(); }Copy the code

The core content of AQS is involved here, because the current state of the value is 1, so the current thread that exclusive resources by other threads, the thread needs to wait for, at this time in AQS maintains a queue, it is made of two-way linked list implementation, when a thread needs to wait for resources, just as a node in the queue.

Now that the first thread has finished executing, call unlock() to release the lock:

public void unlock(a) {
    sync.release(1);
}
Copy the code

It calls AQS’s release() method:

public final boolean release(int arg) {
    if (tryRelease(arg)) {
        Node h = head;
        if(h ! =null&& h.waitStatus ! =0)
            unparkSuccessor(h);
        return true;
    }
    return false;
}
Copy the code

AQS determines if there is a node in the queue, and if there is, it obtains a node from the queue and wakes it up.

The above is the whole process of ReentrantLock lock unlock, from the source code is not difficult to find, the bottom of ReentrantLock is all implemented by AQS.

Finally, I conclude with a graph: