preface

Macro understanding of AQS

AQS isAbstractQueuedSynchronizerClass (abstract queue synchronizer)

  • Realize the separation of read lock and write lock
  • High similarity with synchronized keyword underlying implementation (more than 80%)
  • The e remaining 20% are extended features such as Condition and ReentrantLock, ReentrantReadWriteLock.
  • Most of the components in JUC are derived from AQS

What does AQS do roughly?

  • Provides a framework for implementing blocking locks and associated synchronizers (semaphores, events, etc.) that rely on first-in, first-out (FIFO) wait queues. This class is intended to be the foundation for most synchronizers that rely on a single atomic int value to represent state. Subclasses must define protected methods that change this state and define what this state means in terms of getting or releasing this object. Given this, other methods in this class implement all wait and blocking mechanisms. Subclasses can maintain other state (state) fields, but only atomically updated int values using methods getState, setState, and compareAndSetState are tracked in terms of synchronization.
  • It has a first in, first out (FIFO) waiting queue
  • Implement synchronization mechanism
  • Use an atomic int value for state.
  • Subclasses need to define protected methods that change state
  • Different subclasses may have different meanings for state

How does ReentrantLock use AQS

preface

About fair locks and unfair locks

Fair lock: multiple threads acquire locks in the same order as they apply for locks, and the thread directly enters the queue to queue. The thread is always the first in the queue to obtain locks.

  • Advantages: All threads get resources and do not starve to death in the queue.
  • Disadvantages: Throughput drops dramatically, all but the first thread in the queue blocks, and it is expensive for the CPU to wake up blocked threads.

Unfair lock: When multiple threads attempt to acquire a lock, they will directly try to obtain it. If they fail to obtain it, they will enter the blocking queue. If they can obtain it, they will directly obtain the lock.

  • Advantages: can reduce the CPU wake up thread overhead, the overall throughput efficiency will be higher, CPU does not have to wake up all threads, will reduce the number of wake up threads.
  • Disadvantages: As you may have noticed, this can cause a thread in the middle of the queue to remain without a lock or for a long time, leading to starvation.

AQS role in ReentrantLock

AQS is the ReentryLock member variable Sync

Reentrant lock source lock analysis

  • Look at the constructor of ReentrantLock first

  • Sync has two (version) branches, fair and unfair

Starting with Sync fair, how does a thread acquire a lock

  • About the meaning of status in ReentryLock
public class AQSTest {
    private final Lock lock = new ReentrantLock();
    private void method(a){// When method is executed, the lock is re-entered 5 times, so status equals 5
        try{
            lock.lock();
            Thread.sleep(2);
            method2();// Each method has a lock
            method3();A thread can re-enter multiple locks
            method4();
            method5();
            System.out.println("method is over by "+Thread.currentThread().getName());
        }catch (InterruptedException e){
            e.printStackTrace();
        }finally{ lock.unlock(); }}private void method2(a) {
        try {
            lock.lock();
            Thread.sleep(2);
            System.out.println("method2 is over by " + Thread.currentThread().getName());
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
// finally {
// lock.unlock();
/ /}
    }
    private void method3(a) {
        try {
            lock.lock();
            Thread.sleep(2);
            System.out.println("method3 is over by " + Thread.currentThread().getName());
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
// finally {
// lock.unlock();
/ /}
    }
    private void method4(a) {
        try {
            lock.lock();
            Thread.sleep(2);
            System.out.println("method4 is over by " + Thread.currentThread().getName());
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
// finally {
// lock.unlock();
/ /}
    }
    private void method5(a) {
        try {
            lock.lock();
            Thread.sleep(2);
            System.out.println("method4 is over by " + Thread.currentThread().getName());
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
// finally {
// lock.unlock();
/ /}
    }
    public static void main(String[] args) {
        AQSTest aqsTest = new AQSTest();
        Runnable runnable = aqsTest::method;
        IntStream.range(0.10).forEach(i -> newThread(runnable).start()); }}Copy the code

There are as many unlock locks as there are unlock locks, otherwise there will be deadlocks, don’t even think about that. This is just a convenient way to illustrate the meaning of state.

In Sync, how does the thread acquire the lock

  • It’s not that different from the fair version of Sync

Analysis of reentrant lock acquiring release lock

  • Since acquiring the lock is state+1, releasing the lock is state-1.

summary

For ReentrantLock, the execution logic is as follows:

  • Try to acquire the lock on the object, and if it does not (meaning that another thread already holds the lock and has not released it), it will enter the blocking queue of the AQS.
  • If obtained, the lock is treated differently depending on whether it is a fair or unfair lock:
    • If the lock is fair, the thread is placed directly at the end of the AQS blocking queue.
    • If the lock is unfair, the thread will first attempt the CAS operation, and if successful, it will directly acquire the lock. If it fails, it is placed at the end of the blocking queue in the same manner as a fair lock.
  • When unlock is released, release is called to subtract the value of the state member variable by one. If state is not zero, release is completed. If the state value is 0 after the subtracting operation, the unpark method of LockSupport is called to wake up the first subsequent thread in the waiting queue (pthread _mutex_UNLOCK) so that it can acquire the lock of the object (when release, The logic for fair and unfair locks is the same); The reason why state may not be zero after a release call is that ReentrantLock is a ReentrantLock, which means that the thread can call the lock method multiple times, resulting in increments of state with each call.
  • For ReentrantLock, the lock is essentially the operation on the state member variable in AQS: +1 indicates the lock; For the member variable -1, the lock is released.