天天看点

Java并发 ReentrantLock(重入锁)之公平锁源码解析 超详细!!!锁操作释放锁

上一篇讲的是ReentrantLock的非公平锁,这篇博客就讲讲RenntrantLock的公平锁。

公平锁,就是线程老老实实的排队,不会尝试争抢资源。

还是举上厕所的栗子(通俗易懂,牛的一批):你在公共厕所排队上厕所,突然又有一个人进来,这回这个人讲究的很,没有去挨个开厕所门,而是直接排在你后面,等轮到他了他再上厕所。

其实公平锁与非公平锁的操作大部分都差不多,只是在尝试获取锁资源的地方不一致,其他的排队与唤醒等操作都是一致的。所以本篇博客就只讲公平锁与非公平锁不一致的地方供大家参考。一致的部分我就不详细讲了,如有不懂的看上一篇博客就好。

锁操作

先上我的测试代码

public static void main(String[] args) {
	ReentrantLock reentrantLock = new ReentrantLock(true);
	reentrantLock.lock();
	reentrantLock.unlock();
}
           

这回,在创建ReentrantLock对象的时候我带了一个布尔参数true。表示我要创建的锁是公平锁。

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

在公平锁尝试获取锁的时候有什么不一致的呢?接下来详细的分析一下

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

如果大家看了上一篇非公平锁的博客,就可以一眼看出不一致的地方。在非公平锁里面,上锁首先就尝试进行锁状态数的更新。如果更新失败了,才会去尝试获取锁,而在公平锁里面,则直接就进行尝试获取锁,不管当前锁是不是空闲的。

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

在尝试获取锁这里公平锁与非公平锁都是一致的。接下来看tryAcquire(arg)方法,这个方法与非公平锁的tryAcquire方法有一些不一致

protected final boolean tryAcquire(int acquires) {
	final Thread current = Thread.currentThread();
	int c = getState();
	if (c == 0) {
		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;
}
           

第一步,获取当前线程,其次获取当前锁的状态数。

其次开始判断当前状态数是否为0,如果为0则进一步判断当前是否有进程在排队如果没有则尝试进行状态数的更新。如果更新成功了则将独占锁的线程设置成当前线程。如果当前状态数不为0,则接着判断当前线程与独占锁线程是不是同一个,如果是同一个线程,则可以进入到锁里面,将状态数加一,判断状态数有没有超过重入最大次数,超过了则抛异常,没有就将状态数更新,然后返回true。

都不符合,则返回false。

注:标红的地方就是公平锁与非公平锁不一致的地方。

接下来看一下hasQueuedPredecessors()具体操作

public final boolean hasQueuedPredecessors() {
	// The correctness of this depends on head being initialized
	// before tail and on head.next being accurate if the current
	// thread is first in queue.
	Node t = tail; // Read fields in reverse initialization order
	Node h = head;
	Node s;
	return h != t &&
		((s = h.next) == null || s.thread != Thread.currentThread());
}
           

操作还是比较简单的 先获取到当前链表的头与尾节点,如果头结点不等于尾节点并且h.next为null或者头节点的后继节点存储的线程不是当前线程。则判断当前有线程在排队。

注:h != t && h.next == null 为什么成立就会判断当前有线程在排队呢?原因就是h != t 表示头结点与尾节点之间还有其他节点。在非公平锁里面有一个enq方法,在添加节点的时候是先设置前置节点,然后在设置后继节点。可能会出现执行到获取h.next的时候刚好另一个线程设置完前置节点还没有设置后继节点。所以会出现后继节点为null的情况。

当线程尝试获取锁失败的时候,后续的操作就与非公平锁一致了,先在链表中添加节点,然后再进行尝试排队。排队成功后,将这个线程挂起。

至此公平锁的锁源码就剖析完成了

释放锁

关于释放锁的操作,公平锁与非公平锁一致。所以看非公平锁的解析就可以。

PS:如有不正确的地方,欢迎同学指出。共同进步,谢谢