“全栈2019”Java多线程第二十八章:公平锁与非公平锁详解

101 阅读7分钟

难度

初级

学习时间

30分钟

适合人群

零基础

开发语言

Java

开发环境

  • JDK v11
  • IntelliJ IDEA v2018.3

友情提示

  • 本教学属于系列教学,内容具有连贯性,本章使用到的内容之前教学中都有详细讲解。
  • 本章内容针对零基础或基础较差的同学比较友好,可能对于有基础的同学来说很简单,希望大家可以根据自己的实际情况选择继续看完或等待看下一篇文章。谢谢大家的谅解!

1.温故知新

前面在《“全栈2019”Java多线程第二十四章:等待唤醒机制详解》一章中介绍了等待唤醒机制

《“全栈2019”Java多线程第二十五章:生产者与消费者线程详解》一章中介绍了生产者与消费者线程

《“全栈2019”Java多线程第二十六章:同步方法生产者与消费者线程》一章中介绍了用同步方法来实现生产者与消费者线程例子

《“全栈2019”Java多线程第二十七章:Lock获取lock/释放unlock锁》一章中介绍了Lock获取lock/释放unlock锁。

现在我们来讲解公平锁与非公平锁

2.什么是公平锁与非公平锁?

公平锁和非公平锁其实说的是获取锁的机会是不是对每个等待线程都公平

这就好比一群学生都在竞争一个答题名额:

“全栈2019”Java多线程第二十八章:公平锁与非公平锁详解

每次都是同一个学生抢到,这对其他学生不公平。

公平锁就是获取锁的机会对每个等待线程都公平

非公平锁就是获取锁的机会对每个等待线程都不公平

3.隐式锁非公平锁例子

我们先来看看非公平锁的例子,然后下一小节将非公平锁的例子改写为公平锁。

非公平锁例子很简单,这里我们先写一个匿名内部类实现Runnable接口的对象:

“全栈2019”Java多线程第二十八章:公平锁与非公平锁详解

先不着急把run()方法写好,先来把线程创建好。

需要锁的地方一定是多个线程,所以这里创建3个线程,并把runnable对象传递给线程:

“全栈2019”Java多线程第二十八章:公平锁与非公平锁详解

紧接着,我们启动线程的代码也写了:

“全栈2019”Java多线程第二十八章:公平锁与非公平锁详解

接下来,我们来完善run()方法内部。在run()方法内部需要做的是,让这些线程不停地去竞争这把锁,在竞争的过程中一定有其他两个线程在等待这把锁,因为在任何时刻有且仅有一个线程拥有这把锁,所以,不停地做某事就用while循环:

“全栈2019”Java多线程第二十八章:公平锁与非公平锁详解

其次是我们要让线程们去竞争锁,那就必须要有同步

“全栈2019”Java多线程第二十八章:公平锁与非公平锁详解

接下来,我们在同步代码块里面输出一句话,格式就是当前线程名称+其他语句:

“全栈2019”Java多线程第二十八章:公平锁与非公平锁详解

最后,我们让线程执行到同步代码块里面的时候先睡1秒钟,因为担心执行太快,打印输出语句太多不方便观察:

“全栈2019”Java多线程第二十八章:公平锁与非公平锁详解

好了,例子写完了,运行程序,执行结果:

“全栈2019”Java多线程第二十八章:公平锁与非公平锁详解

从运行结果来看,非公平锁的效果产生了,从头到尾都是“Thread-0”在运行,其他两个线程从未拿到过锁。获取锁的机会是很不公平的。

该例子中还有一个特别重要的知识点:隐式锁是非公平锁

隐式锁,即synchronized关键字用的锁。在上一章《“全栈2019”Java多线程第二十七章:Lock获取lock/释放unlock锁》中有介绍过,感兴趣的小伙伴可以前去阅读。

4.显式锁非公平锁例子

显式锁即Lock。在上一章《“全栈2019”Java多线程第二十七章:Lock获取lock/释放unlock锁》中也有介绍过,感兴趣的小伙伴可以前去阅读。

下面我们就来用显式锁改写上一小节例子。

首先,我们创建出显式锁对象:

“全栈2019”Java多线程第二十八章:公平锁与非公平锁详解

其次,我们将同步代码块开始的地方换成lock.lock(),同步代码块结束的地方换成lock.unlock()。即这两个地方:

“全栈2019”Java多线程第二十八章:公平锁与非公平锁详解

换完之后的样子:

“全栈2019”Java多线程第二十八章:公平锁与非公平锁详解

好了,例子用显式锁改写完成。

接下来运行程序,观察执行结果:

“全栈2019”Java多线程第二十八章:公平锁与非公平锁详解

从运行结果来看,符合预期。

为什么说符合预期?

因为显式锁默认就是非公平锁,所以说符合我们的预期。

难道显式锁还有公平锁?

是的,这就是我们使用显式锁Lock的好处。

5.显式锁公平锁例子

将显式锁非公平锁变成公平锁只需一步,在创建ReentrantLock对象时,给构造方法参数里传入true即可

下面,我们来试试:

“全栈2019”Java多线程第二十八章:公平锁与非公平锁详解

例子改写完成,是不是很简单。

接下来运行程序,观察执行结果:

“全栈2019”Java多线程第二十八章:公平锁与非公平锁详解

从运行结果来看,符合预期。每个线程都获取到了锁。所以说,公平锁是对每一个等待获取锁的线程都是公平的。

6.释放锁操作应该放在finally代码块里面

建议大家把释放锁的操作放在finally代码块里面:

“全栈2019”Java多线程第二十八章:公平锁与非公平锁详解

7.ReentrantLock(boolean fair)构造方法

回过头来,我们再来看看ReentrantLock类的ReentrantLock(boolean fair)构造方法长什么样:

“全栈2019”Java多线程第二十八章:公平锁与非公平锁详解

将注释翻译成中文:

“全栈2019”Java多线程第二十八章:公平锁与非公平锁详解

中文注释全文:

使用给定的公平策略创建ReentrantLock的实例。

去掉注释版:

“全栈2019”Java多线程第二十八章:公平锁与非公平锁详解

参数fair的中文意思是公平

当我们设置fair为true时,构造方法会创建一个公平同步对象:

“全栈2019”Java多线程第二十八章:公平锁与非公平锁详解

FairSync类是一个静态内部类:

“全栈2019”Java多线程第二十八章:公平锁与非公平锁详解

FairSync是一个公平锁的同步对象。它里面会将正在等待该同步对象的线程用队列记录下来。具体请看这个类:

“全栈2019”Java多线程第二十八章:公平锁与非公平锁详解

上图Node类这就是记录每个正在等待锁的节点信息

里面有排在它前面的节点:

“全栈2019”Java多线程第二十八章:公平锁与非公平锁详解

还有排在它后面的节点:

“全栈2019”Java多线程第二十八章:公平锁与非公平锁详解

以及当前节点的线程是谁:

“全栈2019”Java多线程第二十八章:公平锁与非公平锁详解

如此一来,新的等待同步锁的线程加入的话记录在next节点即可。

如果是要取同步锁的话,我们只需判断当前节点的前面还有没有节点即可:

“全栈2019”Java多线程第二十八章:公平锁与非公平锁详解

如果前面有节点的话,就不尝试获取锁了。本来tryAcquire(int acquires)方法就是尝试性获取同步锁,如果获取到同步锁返回true,否则返回false。

分析到此为止,更深入的分析后面讲解ReentrantLock类的时候再补充。

最后,希望大家可以把这个例子照着写一遍,然后再自己默写一遍,方便以后碰到类似的面试题可以轻松应对。

祝大家编码愉快!

GitHub

本章程序GitHub地址:https://github.com/gorhaf/Java2019/tree/master/Thread/Lock

总结

  • 公平锁和非公平锁其实说的是获取锁的机会是不是对每个等待线程都公平。
  • 公平锁就是获取锁的机会对每个等待线程都公平。
  • 非公平锁就是获取锁的机会对每个等待线程都不公平。
  • 隐式锁(即synchronized关键字用的锁)是非公平锁。
  • 显式锁默认是非公平锁。
  • 将显式锁非公平锁变成公平锁只需一步,在创建ReentrantLock对象时,给构造方法参数里传入true即可。

至此,Java中公平锁与非公平锁相关内容讲解先告一段落,更多内容请持续关注。

答疑

如果大家有问题或想了解更多前沿技术,请在下方留言或评论,我会为大家解答。

上一章

“全栈2019”Java多线程第二十七章:Lock获取lock/释放unlock锁

下一章

“全栈2019”Java多线程第二十九章:可重入锁与不可重入锁详解

学习小组

加入同步学习小组,共同交流与进步。

  • 方式一:关注头条号Gorhaf,私信“Java学习小组”。
  • 方式二:关注公众号Gorhaf,回复“Java学习小组”。

全栈工程师学习计划

关注我们,加入“全栈工程师学习计划”。

“全栈2019”Java多线程第二十八章:公平锁与非公平锁详解

版权声明

原创不易,未经允许不得转载!