ReentrantLock source code analysis and AQS principle

ReentrantLock source code analysis and AQS principle

ReentrantLock source code analysis

ReentrantLock ( reentrant mutex ). Reentrant lock, also known as recursive lock, means that when the same thread acquires the lock in the outer method, the inner method of the thread will automatically acquire the lock (provided that the lock object must be the same object or class). Blocked because it has been acquired before but has not been released.

We start from the constructor and analyze step by step.

The two constructors of ReentrantLock use unfair sync objects by default

public ReentrantLock() {
    
    
        sync = new NonfairSync();
}
    
public ReentrantLock(boolean fair) {
    
    
        sync = fair ? new FairSync() : new NonfairSync();
}

Look at the non-fair synchronization class NonfairSync inherited from Sync

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

        /**
         * 非公平锁:线程加锁时直接尝试获取锁,获取不到才会到等待队列的队尾排队等待
         */
        final void lock() {
    
    
            if (compareAndSetState(0, 1))//CAS
                setExclusiveOwnerThread(Thread.currentThread());//设置所有者线程为当前线程
            else
                acquire(1);//失败后尝试获取锁。
        }

        protected final boolean tryAcquire(int acquires) {
    
    
            return nonfairTryAcquire(acquires);
        }
    }

compareAndSetState(0, 1). 0 is the expected value and 1 is the updated value. As long as the expected value is equal to the current synchronization state state, the state is updated to update (updated value).

Here state is the state in which the current thread acquires the lock. First look at the description of state.

private volatile int state;//state是Volatile修饰的,用于保证一定的可见性和有序性。
  1. State is 0 when it is initialized, which means that no thread holds the lock.
  2. When a thread holds the lock, the value will be +1 on the original basis. When the same thread acquires the lock multiple times, it will get +1 multiple times. Here is the concept of reentrancy.
  3. Unlock is also -1 for this field, until it reaches 0, this thread releases the lock.

Therefore, compareAndSetState(0, 1) here is to determine whether the current thread state is 0, if it is 0, the lock is successfully acquired, and the state is updated to 1.

protected final boolean compareAndSetState(int expect, int update) {
    
    
        return unsafe.compareAndSwapInt(this, stateOffset, expect, update);
    }

What if the lock acquisition fails? That is, the state of the current thread is not 0, then acquire(1) will be executed ;

//arg代表获取锁的次数
public final void acquire(int arg) {
    
    
    	//tryAcquire(arg)为true,代表获取锁成功(当前线程为独占线程)。
    	//获取锁失败后,再执行acquireQueued将线程排队。
        if (!tryAcquire(arg) &&
            //addWaiter方法其实就是把对应的线程以Node的数据结构形式加入到双端队列里,返回的是一个包含该线程的Node。而这个Node会作为参数,进入到acquireQueued方法中。acquireQueued方法可以对排队中的线程进行“获锁”操作。
            acquireQueued(addWaiter(Node.EXCLUSIVE), arg))
            //中断当前线程
            selfInterrupt();
    }

These methods are all called by the (non)fair synchronization class in ReentrantLock, while (Non)fairSync inherits from the Sync class, and the Sync class inherits from AQS, so these methods are defined in AQS. For example, the above tryAcquire method is rewritten by ReentrantLock, and a direct call to AQS's tryAcquire will throw an UnsupportedOperationException.

Insert picture description here

You can see that both the fair synchronization class and the unfair synchronization class in ReentrantLock override tryAcquire.

So acquire calls tryAcquire which has been rewritten in NonfairSync

//NonfairSync类里最后一个方法,当前acquires=1
protected final boolean tryAcquire(int acquires) {
    
    
    return nonfairTryAcquire(acquires);
}

final boolean nonfairTryAcquire(int acquires) {
    
    
    		//当前线程
            final Thread current = Thread.currentThread();
    		//当前state值
            int c = getState();
    		//如果没有线程获取锁
            if (c == 0) {
    
    
                if (compareAndSetState(0, acquires)) {
    
    
                    //获取锁成功,并将当前线程设置为独占线程
                    setExclusiveOwnerThread(current);
                    return true;
                }
            }
    		//如果当前线程为独占线程,则将state加1。
            else if (current == getExclusiveOwnerThread()) {
    
    
                int nextc = c + acquires;
                if (nextc < 0) // overflow
                    throw new Error("Maximum lock count exceeded");
                setState(nextc);
                return true;
            }
            return false;
        }

Before looking at the addWaiter method, we must first look at the Node node, that is, which queue is added to the thread that has not acquired the lock.

This involves the core idea of ​​AQS : If the requested shared resource is free, then the thread that currently requests the resource is set as an effective worker thread, and the shared resource is set to a locked state; if the shared resource is occupied, a certain amount of blocking is required Wait for the wake-up mechanism to ensure lock allocation. This mechanism is mainly implemented by a variant of the CLH queue—virtual two-way queue (FIFO)—to add threads that temporarily cannot obtain locks into the queue.

AQS uses a Volatile int type state to represent the synchronization state, completes the queuing work for resource acquisition through the built-in FIFO queue, and completes the modification of the State value through CAS.

The basic data structure in AQS- Node , the node in the virtual two-way queue (FIFO).

static final class Node {
    
    
    /** 标志线程以共享/独占方式等待锁 */
    static final Node SHARED = new Node();
    static final Node EXCLUSIVE = null;

    /** 表示线程获取锁的请求已经取消 */
    static final int CANCELLED =  1;
    /** 表示线程已经准备好,就等资源释放了(被唤醒了) */
    static final int SIGNAL    = -1;
    /** 表示节点在等待队列中,节点线程等待唤醒 */
    static final int CONDITION = -2;
    /** 当前线程处在SHARED情况下,该字段才会使用(其他操作介入,也要确保传播继续) */
    static final int PROPAGATE = -3;
    
    //当前节点在队列中的状态
    volatile int waitStatus;
    //前驱指针
    volatile Node prev;
    //后继指针
    volatile Node next;
    //表示处于该节点的线程
    volatile Thread thread;
    //指向下一个处于CONDITION状态的节点
    Node nextWaiter;
    
    /** 返回前驱节点,没有则抛出空指针异常 */
    final Node predecessor() throws NullPointerException {
    
    
        Node p = prev;
        if (p == null)
            throw new NullPointerException();
        else
            return p;
    }
    
    /** 几个Node构造函数 */
    //用来初始化头节点或SHARED标志
    Node() {
    
        
    }

    //用于addWaiter方法
    Node(Thread thread, Node mode) {
    
        
        this.nextWaiter = mode;
        this.thread = thread;
    }

    //用于Condition
    Node(Thread thread, int waitStatus) {
    
     
        this.waitStatus = waitStatus;
        this.thread = thread;
    }
}
//队列头节点
private transient volatile Node head;
//队列尾节点
private transient volatile Node tail;

Now let's look at how the addWaiter method adds threads to the deque.

//这里的mode就是Node.EXCLUSIVE,表示独占模式
private Node addWaiter(Node mode) {
    
    
    //通过当前的线程和锁模式新建一个节点。
    Node node = new Node(Thread.currentThread(), mode);
    //pred指向尾节点tail
    Node pred = tail;
    //如果尾节点不为空,则将当前节点插入到尾节点后面(设为尾节点),并返回node
    if (pred != null) {
    
    
        node.prev = pred;
        if (compareAndSetTail(pred, node)) {
    
    
            pred.next = node;
            return node;
        }
    }
    //如果Pred指针是Null(说明等待队列中没有元素),或者当前Pred指针和Tail指向的位置不同(说明已经被别的线程修改),则进入enq初始化head节点,并将node设为尾节点。
    enq(node);
    return node;
}

Note that in the doubly linked list, the first node (head node) is a virtual node, which does not actually store any information, but just a place. The true first node with data starts at the second node.

Going back to the above acquireQueued(addWaiter(Node.EXCLUSIVE), arg)) method, acquireQueued will keep the threads put in the queue to acquire the lock until the acquisition succeeds or the acquisition is no longer needed (interrupted).

So when can the threads in the queue acquire the lock? What if I can’t get it?

final boolean acquireQueued(final Node node, int arg) {
    
    
	// 标记是否成功拿到资源
	boolean failed = true;
	try {
    
    
		// 标记等待过程中是否中断过
		boolean interrupted = false;
		// 开始自旋,要么获取锁,要么中断
		for (;;) {
    
    
			// 获取当前节点的前驱节点
			final Node p = node.predecessor();
			// 如果p是头结点,说明当前节点在真实数据队列的首部,就尝试获取锁(别忘了头结点是虚节点)
			if (p == head && tryAcquire(arg)) {
    
    
				// 获取锁成功,头指针移动到当前node
				setHead(node);
				p.next = null; // help GC
				failed = false;
				return interrupted;
			}
			// 说明p为头节点且当前没有获取到锁(可能是非公平锁被抢占了)或者是p不为头结点,这个时候就要判断当前node是否要被阻塞(被阻塞条件:前驱节点的waitStatus为-1),防止无限循环浪费资源。
			if (shouldParkAfterFailedAcquire(p, node) && parkAndCheckInterrupt())
				interrupted = true;
		}
	} finally {
    
    
		if (failed)//未成功拿到资源,设置当前节点状态为CANCELLED
			cancelAcquire(node);
	}
}

// 靠前驱节点判断当前线程是否应该被阻塞,true阻塞,false不阻塞
private static boolean shouldParkAfterFailedAcquire(Node pred, Node node) {
    
    
	// 获取前驱结点的节点状态
	int ws = pred.waitStatus;
	// 说明前驱结点处于唤醒状态,此时需要阻塞当前节点,返回true
	if (ws == Node.SIGNAL)
		return true; 
	// 通过枚举值我们知道waitStatus>0是取消状态
	if (ws > 0) {
    
    
		do {
    
    
			// 循环向前查找取消节点,把取消节点从队列中剔除
			node.prev = pred = pred.prev;
		} while (pred.waitStatus > 0);
        //直到前面某一个节点非取消节点,将非取消节点连接当前节点
		pred.next = node;
	} else {
    
    //前驱节点waitStatus为0或-3。
		// 设置前驱节点等待状态为SIGNAL
		compareAndSetWaitStatus(pred, ws, Node.SIGNAL);
	}
	return false;
}

//parkAndCheckInterrupt主要用于挂起当前线程,阻塞调用栈,返回当前线程的中断状态。
private final boolean parkAndCheckInterrupt() {
    
    
    LockSupport.park(this);
    return Thread.interrupted();
}

The first two problems are solved: as long as the predecessor node is the head node, try to acquire the lock. The predecessor node is not the head node or fails to acquire the lock, then it is determined whether the current thread needs to be blocked. If the predecessor node status is SIGNAL, it indicates that the predecessor node is ready to acquire resources, so the current node is blocked; if the predecessor node is in the cancel state CANCELLED (no longer acquiring resources), remove all the predecessor nodes in the cancelled state, and continue spinning to try to acquire the lock; if If the waitStatus of the precursor node is 0 (default) or -3, change the status of the precursor node to SIGNAL and continue spinning.

Reference link : Meituan technical team: AQS principle and application from the realization of ReentrantLock

Guess you like

Origin blog.csdn.net/ZMXQQ233/article/details/108187499