排队打饭:公平锁和非公平锁(面试) (2)

如上代码:我们在代码第13行中定义了Lock LOCK = new ReentrantLock(false);ReentrantLock的参数是false表示非公平锁,上面代码需要用LOCK.lock()加锁,LOCK.unlock()解锁,需要放入try,finally代码块中,目的是如果try中加锁后代码发生异常锁最终执行LOCK.unlock(),锁总能被释放。主类中让编号001至005五个同学同时去打饭,得到其中一种执行结果如下图所示,001->004->005->003->002同学先去排队,但打饭的顺序是001->005->004->003->002,这里005同学插队了,插到004前面。我们详细分析执行过程:001先来抢到锁打饭了并释放了锁,接下来本应该是004抢到锁,因为它先排队,但005却在004之前抢到锁,打饭了,005比004后来,却先打饭,这就是不公平锁,后面的执行结果按先来后到执行,程序结束。我们用代码证明了ReentrantLock是非公平的锁。紧接着我们来看下ReentrantLock另一种作为公平锁的情况。

image.png


ReentrantLock公平锁

基于上面的案例,我们不重复贴代码了,将上述代码中13行的private static final Lock LOCK = new ReentrantLock(false);参数由false改为true,private static final Lock LOCK = new ReentrantLock(true);无论执行多少次可以得出一个结论:先排队的童鞋能先打饭,不允许插对体现的就是公平锁。

image.png


ReentrantLock底层原理

ReentrantLock是基于AbstractQueuedSynchronizer(抽象队列同步器,简称aqs)实现的,aqs底层维护了一个带头的双向链表,用来同步线程,链表每个节点用Node表示,每个Node会记录线程信息,上下节点,节点状态等信息,aqs控制Node的生命周期。如下图所示,aqs也包含条件队列,锁和条件队列(condition)是一对多的关系,也就是说一个锁可以对应多个条件队列,线程间的通信在条件队列里通过await,single/singleAll方法控制,synchronized只有一个条件队列用wait,notify/notifyAll来实现,这里不展开说了,《母鸡下蛋实例:多线程通信生产者和消费者wait/notify和condition/await/signal条件队列》和《Synchronized用法原理和锁优化升级过程(面试)》可以看我文章,里面有大量清晰简单案例。条件队列也是以链表形式存在。Lock是基于juc包实现,synchronized是本地方法基于c++实现。

image.png


总结

今天用生活中的例子转化成代码,详细的介绍了公平锁和非公平锁,并简单的介绍了aqs实现原理,给您的建议是认真把代码敲一遍,如果执行了一遍代码应该能看明白,喜欢的请点赞加关注哦。我是叫练【公众号】,边叫边练。

image.png

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

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