九、ReentrantLock加锁流程之lock方法

101 阅读7分钟

ReentrantLock加锁流程

流程概述:

  1. 对于非公平锁而言,首先判断state值是否为0。如果是,竞争锁资源;如果不是,判断持有锁的线程是否是当前线程。如果是,锁重入;如果不是,线程放入AQS链表中
  2. 对于公平锁而言,首先判断state值是否为0。如果是,还需判断AQS链表中是否有等待的线程,如果有,还需判断head后面的节点是否是当前线程,如果是,才能竞争锁资源。后续的流程与非公平锁一致
  3. 线程放入AQS链表中。首先判断尾节点是否为null,如果不是,放到尾节点后面,如果为null,创建head节点,将线程放到head节点后面
  4. 线程放到AQS链表中后,判断当前线程前一个节点是否是head节点,如果是,竞争锁资源,如果不是,判断前一个节点的waitStatus值是否<=0,如果是,将前一个节点的waitStatus值改成-1,并且挂起当前线程。前一个节点的waitStatus值是1,那么会将前一个节点从AQS链表中删除,直到当前节点挂在正常节点的后面

非公平锁

  1. 生成非公平锁的语句: ReentrantLock lock = new ReentrantLock();
  2. lock方法中会执行sync.lock()方法
  3. 使用CAS操作将state的值从0变成1
  4. 如果成功,代表加锁成功,会将exclusiveOwnerThread属性值设置成当前线程,代表当前线程获得锁资源
  5. 如果失败,执行acquire()方法
static final class NonfairSync extends Sync {
	private static final long serialVersionUID = 7316153563782823691L;

	final void lock() {
		// 使用CAS,尝试将state值从0变成1
		if (compareAndSetState(0, 1))
			// 加锁成功
			setExclusiveOwnerThread(Thread.currentThread());
		else
			// 加锁失败
			acquire(1);
	}
}

公平锁

  1. 生成公平锁的语句:ReentrantLock lock = new ReentrantLock(true);
  2. lock方法中会执行sync.lock()方法
  3. 执行acquire()方法
static final class FairSync extends Sync {
	private static final long serialVersionUID = -3000897897090466540L;

	final void lock() {
		acquire(1);
	}
}

acquire()方法

  1. tryAcquire()方法是在链表中没有挂起线程的情况下,让当前线程再次尝试获取锁资源,如果成功则返回,否则继续
  2. addWaiter()方法是将线程封装成node节点后,添加到双向链表的尾部
  3. acquireQueued()方法会判断此线程是否是链表head伪节点后面的第一个节点,如果是,则再次尝试获取锁资源,如果获取失败,则挂起线程;如果不是,直接挂起
  4. 最后,将线程的中断标志位设置成true
public final void acquire(int arg) {
	if (!tryAcquire(arg) &&
		acquireQueued(addWaiter(Node.EXCLUSIVE), arg))
		selfInterrupt();
}

tryAcquire()方法

非公平锁
  1. 执行nonfairTryAcquire()方法
  2. 如果state值为0,当前线程尝试获取锁资源,获取成功直接返回,获取失败则继续执行后续代码
  3. 判断获取锁资源的线程是否是当前线程,如果不是就返回
  4. 如果是当前线程,state的值继续加1,ReentrantLock支持可重入锁
final boolean nonfairTryAcquire(int acquires) {
	final Thread current = Thread.currentThread();
	int c = getState();
	if (c == 0) {
		// 尝试获取锁资源
		if (compareAndSetState(0, acquires)) {
			setExclusiveOwnerThread(current);
			return true;
		}
	}
	else if (current == getExclusiveOwnerThread()) {
		// 当前线程拿到锁资源,state+1
		int nextc = c + acquires;
		// nextc < 0 表示state的值已经达到int类型的最大值,再加1就变成了负数
		if (nextc < 0) // overflow
			throw new Error("Maximum lock count exceeded");
		setState(nextc);
		return true;
	}
	return false;
}
公平锁
  1. 重写了tryAcquire()方法并且执行
  2. 如果state值为0,判断双向链表中是否没有等待线程,如果没有,尝试获取锁;如果有,判断第一个等待的线程是否是当前线程,如果是尝试获取锁资源
  3. 判断获取锁资源的线程是否是当前线程,如果不是就返回
  4. 如果是当前线程,state的值继续加1,ReentrantLock支持可重入锁
protected final boolean tryAcquire(int acquires) {
	final Thread current = Thread.currentThread();
	int c = getState();
	if (c == 0) {
		// hasQueuedPredecessors()方法是判断双向链表中是否没有等待线程,
		// 或者第一个等待的线程是否是当前线程,如果满足则返回false
		if (!hasQueuedPredecessors() &&
			compareAndSetState(0, acquires)) {
			setExclusiveOwnerThread(current);
			return true;
		}
	}
	else if (current == getExclusiveOwnerThread()) {
		int nextc = c + acquires;
		if (nextc < 0)
			throw new Error("Maximum lock count exceeded");
		setState(nextc);
		return true;
	}
	return false;
}

addWaiter()方法

公平锁和非公平锁的addWaiter()方法相同

  1. 判断双向链表中的尾节点是否为null,如果不为null,将当前节点添加到尾节点后面
  2. 如果为null,执行enq()方法
  3. enq()方法里有个死循环,判断尾节点是否为null,如果是,生成一个伪节点
  4. 然后将当前节点添加到伪节点后面
private Node addWaiter(Node mode) {
	Node node = new Node(Thread.currentThread(), mode);
	Node pred = tail;
	if (pred != null) {
		node.prev = pred;
		// 将当前节点添加到尾节点后面
		if (compareAndSetTail(pred, node)) {
			pred.next = node;
			return node;
		}
	}
	enq(node);
	return node;
}

private Node enq(final Node node) {
	for (;;) {
		Node t = tail;
		if (t == null) { // Must initialize
			if (compareAndSetHead(new Node()))
				tail = head;
		} else {
			node.prev = t;
			if (compareAndSetTail(t, node)) {
				t.next = node;
				return t;
			}
		}
	}
}

acquireQueued()方法

  1. acquireQueued()方法里也有个死循环,判断当前节点的前一个节点是否是头节点
  2. 如果是,尝试获取锁资源,如果成功,设置当前节点为头节点
  3. 如果不是,执行shouldParkAfterFailedAcquire()方法
final boolean acquireQueued(final Node node, int arg) {
	// lock()方法加锁,不用管方法中的线程中断逻辑
	boolean failed = true;
	try {
		boolean interrupted = false;
		for (;;) {
			final Node p = node.predecessor();
			if (p == head && tryAcquire(arg)) {
				setHead(node);
				p.next = null; // help GC
				failed = false;
				return interrupted;
			}
			// shouldParkAfterFailedAcquire()方法修改当前节点和前面节点的waitStatus值
			// parkAndCheckInterrupt()方法是将当前线程挂起
			if (shouldParkAfterFailedAcquire(p, node) &&
				parkAndCheckInterrupt())
				interrupted = true;
		}
	} finally {
		if (failed)
			cancelAcquire(node);
	}
}
shouldParkAfterFailedAcquire()方法
  1. 如果当前节点的前一个节点waitStatus值是-1,返回true
  2. 如果当前节点的前一个节点waitStatus值>0,前一个节点从双向链表中删除, 直到前一个节点waitStatus值<=0,将当前节点放到这个节点后面,然后返回false
  3. 如果当前节点的前一个节点waitStatus值是0,则将waitStatus设置成-1,然后返回false
  4. shouldParkAfterFailedAcquire方法返回false后,按照acquireQueued方法逻辑,线程会再次判断前一个节点是否是head节点,如果是则竞争锁资源。这个逻辑保证了unlock方法唤醒的有效节点可以正常竞争锁资源
private static boolean shouldParkAfterFailedAcquire(Node pred, Node node) {
	int ws = pred.waitStatus;
	if (ws == Node.SIGNAL)
		return true;
	if (ws > 0) {
		do {
			node.prev = pred = pred.prev;
		} while (pred.waitStatus > 0);
		pred.next = node;
	} else {
		compareAndSetWaitStatus(pred, ws, Node.SIGNAL);
	}
	return false;
}
parkAndCheckInterrupt()方法
private final boolean parkAndCheckInterrupt() {
    LockSupport.park(this);
    return Thread.interrupted();
}

lock方法加锁流程图

image.png

lock方法加锁逻辑小结

  1. 在锁被释放的情况下,对于公平锁来说,执行tryAcquire方法加锁时,首先判断队列中是否有等待的节点,如果有,判断head节点后面的线程是否是当前线程,如果是,则获取锁,如果不是,则不去尝试获取锁,直接返回false;如果队列为空,尝试获取锁。对于非公平锁来说,无论队列中是否有等待的节点,都会尝试获取锁。tryAcquire方法返回true代表获取锁成功,返回false代表获取锁失败。
  2. 获取锁失败的话,会执行addWaiter方法。此方法无论是公平锁还是非公平锁,逻辑是一样的。将当前线程封装成Node节点,添加到队列的尾部。如果队列为空,首先创建head节点,再将当前线程节点添加到head节点后面。为了确保添加尾部节点时线程安全,使用了CAS机制。如果第一次添加失败,会执行enq方法。这个方法逻辑是个死循环,目的就是添加尾部节点成功。
  3. 当添加尾部节点成功后,执行acquireQueued方法。此方法中会判断当前线程的节点的前一个节点是否是head节点,如果是,当前线程尝试获取锁,获取成功返回true;如果不是,判断前一个节点的waitStatus值是否是1,如果是,表示这个节点是要被移除的节点,那么就将节点移除后,再判断前一个节点的waitStatus值,直到遇到一个节点的waitStatus值小于等于0,才将当前节点设置到此节点后面,并且修改此节点的waitStatus值为-1,当前节点的改成0。