这是我参与更文挑战的第 17 天,活动详情查看:更文挑战
作者:花Gie
微信公众号:Java开发零到壹
前言
多线程系列我们前面已经更新过很多章节,强烈建议小伙伴按照顺序学习:
前一章介绍了悲观锁、乐观锁以及CAS等概念,今天就继续介绍另外的两种常见的锁—-非公平锁和公平锁。
正文
锁的总览
锁可以从不同的角度进行分类,这些分类并不是互斥的,比如一个人既可以是医生,又可以是父亲,也可以是一样女人。分类总览如图:
公平锁与非公平锁
- 概念
公平锁:多个线程申请获取锁,会按照先后顺序进入队列,排在队首的线程会率先获取锁。
非公平锁:多个线程申请锁时,首先会尝试获取锁,如果获取不到,再进行排队,如果能够直接获取到锁,那么直接获取。
这里举个生活中的常见例子,这样大家就很容易理解。
小伙伴们应该都去过火车站排队买票吧,现在估计都会在网上直接购票,那排队就是一个很公平的,先到先得,这就是公平锁的案例。
如果这时有个乘客1买完票了,乘客2正在掏身份证,此时有个乘客4上来插队,问了售票员一句:去铁岭的票还有吗?这个时候就可以看做是非公平锁了,因为乘客4没有按照先来后到而是直接见缝插针,抢先一步,这就是非公平锁的一种案例。
说完了生活实例,我们接下来看一下这两种锁的原理实现。
这就涉及到我们前面文章经常提到的ReentrantLock,它可实现公平锁和非公平锁两种模式,而ReentrantLock的实现是基于其内部类Sync。
1 | java复制代码public class ReentrantLock implements Lock, java.io.Serializable { |
Sync又有两个子类,FairSync(公平锁)和NonFairSync(非公平锁)。
实现原理
公平锁与非公平锁的原理差距不大,主要是在获取锁时存在差异,公平锁在获取锁之前会先判断等待队列是否为空或者自己是否位于队列头部,该条件通过才能继续获取锁。两种获取锁的方式对应源码如下:
1 | java复制代码//公平锁 |
从上面两个方法源码可以看到,非公平锁在尝试获取锁时,不会调用hasQueuedPredecessors
(判断当前的线程是否位于同步队列的首位,是就返回true,否则返回false)。
1 | java复制代码public final boolean hasQueuedPredecessors() { |
对于非公平锁,只要线程进入了等待队列,队列里面依然是先进先出的原则,和公平锁的顺序是一样的。因为公平锁与非公平锁在释放锁部分代码是共用AQS的代码。
1 | java复制代码private void unparkSuccessor(Node node) { |
代码实现
这里写一个多线程打印日志的案例
1 | java复制代码public class FairLockDemo { |
根据上面6个步骤,我们分别尝试ReentrantLock实现公平锁和非公平锁模式,会出现什么结果呢。
- 使用公平锁模式,即ReentrantLock(true),每个线程按照先后顺序,插入到队列中。如下图按照Thread-0、1、2、3、4打印一遍之后,再次按照该顺序打印第二遍,不会因为步骤5出现线程插队现象:
- 使用非公平锁模式,即ReentrantLock(false),当一个线程执行完步骤4后,会立即尝试获取锁(步骤5),因此该线程会抢先队列中的线程,从而获取到锁。但是整体又是按照Thread-0、1、2、3、4进行执行,这也符合我们上面对非公平锁的分析:
总结
上面是公平锁和非公平锁的介绍,文中涉及到AQS、ReentrantLock,可能小伙伴们不是很理解,不过不用担心,这里我们先熟悉概念,这几张介绍锁的概念也是在为后面AQS进行铺垫。
点关注,防走丢
以上就是本期全部内容,如有纰漏之处,请留言指教,非常感谢。我是花GieGie ,有问题大家随时留言讨论 ,我们下期见🦮。
文章持续更新,可以微信搜一搜 Java开发零到壹 第一时间阅读,并且可以获取面试资料学习视频等,有兴趣的小伙伴欢迎关注,一起学习,一起哈🐮🥃。
原创不易,你怎忍心白嫖,如果你觉得这篇文章对你有点用的话,感谢老铁为本文点个赞、评论或转发一下
,因为这将是我输出更多优质文章的动力,感谢!
参考链接:
本文转载自: 掘金