ReentrantLock 源码分析以及 AQS (一) (4)

公平锁和非公平锁的整体流程大致相同,只是在抢锁之前先判断一下是否已经有人排在前面,如果有的话,就不执行抢锁。我们通过源码追踪到 FairSync.tryAcquire 方法。会发现,多了一个 hasQueuedPredecessors 方法。

hasQueuedPredecessors

这个方法判断逻辑稍微有点复杂,有多种情况。

public final boolean hasQueuedPredecessors() { Node t = tail; Node h = head; Node s; return h != t && ((s = h.next) == null || s.thread != Thread.currentThread()); }

如果 h == t,说明 h 和 t 都为空(此时队列还未初始化)或者它们是同一个节点(说明队列已经初始化,并且只有一个节点,此时为 enq 方法第一次自旋成功后)。此时,返回false。

如果 h != t,则判断 head.next == null 是否成立,如果成立,则返回 true。这种情况发生在有其他线程第一次入队时。在 AQS 的 enq 入队方法,设置头结点成功之后 compareAndSetHead(new Node()) ,还未执行 tail = head 时(仔细想一想为什么?)。此时 tail = null , head = new Node(),head.next = null。

如果 h != t,并且 head.next != null,说明此时队列中至少已经有两个节点,则判断 head.next 是否是当前线程。如果是,返回 false(注意是 false哦,因为用了 !),否则返回 true 。

总结:以上几种情况,只有最终返回 false 时,才会继续往下执行。因为 false,说明没有线程排在当前线程前面,于是通过 CAS 尝试把 state 值设置为 1。若成功,则方法返回。若失败,同样需要去排队。

公平锁和非公平锁区别

举个例子来对比公平锁和非公平锁。比如,现在到饭点了,大家都到食堂打饭。把队列中的节点比作排队打饭的人,每个打饭窗口都有一个管理员,只有排队的人从管理员手中抢到锁,才有资格打饭。打饭的过程就是线程执行的过程。

ReentrantLock 源码分析以及 AQS (一)

如果,你发现前面没有人在排队,那么就可以直接从管理员手中拿到锁,然后打饭。对于公平锁来说,如果你前面有人在打饭,那么你就要排队到他后面(图中B),等他打完之后,把锁还给管理员。那么,你就可以从管理员手中拿到锁,然后打饭了。后面的人依次排队。这就是FIFO先进先出的队列模型。

对于非公平锁来说,如果你是图中的 B,当 A 把锁还给管理员后,有可能有另外一个 D 插队过来直接把锁抢走。那么,他就可以打饭,你只能继续等待了。

所以,可以看出来。公平锁是严格按照排队的顺序来的,先来后到嘛,你来的早,就可以早点获取锁。优点是,这样不会造成某个线程等待时间过长,因为大家都是中规中矩的在排队。而缺点呢,就是会频繁的唤起线程,增加 CPU的开销。

非公平锁的优点是吞吐量大,因为有可能正好锁可用,然后线程来了,直接抢到锁了,不用排队了,这样也减少了 CPU 唤醒排队线程的开销。 但是,缺点也很明显,你说我排队排了好长时间了,终于轮到我打饭了,凭什么其他人刚过来就插到我前面,比我还先打到饭,也太不公平了吧,后边一大堆排队的人更是怨声载道。这要是每个人来了都插到我前面去,我岂不是要饿死了。

独占锁的释放

我们从 ReentrantLock 的 unlock 方法看起:

public void unlock() { //调用 AQS 的 release 方法 sync.release(1); } public final boolean release(int arg) { if (tryRelease(arg)) { Node h = head; //如果头结点不为空,并且 ws 不为 0,则唤起后继节点 if (h != null && h.waitStatus != 0) unparkSuccessor(h); return true; } return false; }

这段逻辑比较简单,当线程释放锁之后,就会唤醒后继节点。 unparkSuccessor 已讲,不再赘述。然后看下 tryRelease 方法,公平锁和非公平锁走的是同一个方法。

protected final boolean tryRelease(int releases) { //每释放一次锁,state 值就会减 1,因为之前可能有锁的重入 int c = getState() - releases; //如果当前线程不是抢到锁的线程,则抛出异常 if (Thread.currentThread() != getExclusiveOwnerThread()) throw new IllegalMonitorStateException(); boolean free = false; if (c == 0) { //只有 state 的值减到 0 的时候,才会全部释放锁 free = true; setExclusiveOwnerThread(null); } setState(c); return free; }

因为,ReentrantLock 支持锁的重入,所以每次重入 state 值都会加 1,相应的每次释放锁, state 的值也会减 1 。所以,这也是为什么每个 lock 方法最后都要有一个 unlock 方法释放锁,它们的个数需要保证相同。

当 state 值为 0 的时候,说明锁完全释放。其他线程才可以有机会抢到锁。

结语

内容版权声明:除非注明,否则皆为本站原创文章。

转载注明出处:https://www.heiqu.com/wpsgyp.html