Loading [MathJax]/jax/output/CommonHTML/config.js
前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
圈层
工具
发布
首页
学习
活动
专区
圈层
工具
MCP广场
社区首页 >专栏 >看完你就明白的锁系列之锁的公平性

看完你就明白的锁系列之锁的公平性

作者头像
cxuan
发布于 2019-10-31 12:51:54
发布于 2019-10-31 12:51:54
1K00
代码可运行
举报
文章被收录于专栏:Java建设者Java建设者
运行总次数:0
代码可运行

这是Java建设者的第 41 篇原创文章

这是看完你就明白的锁系列的第四篇文章

文章一览请看这里

看完你就应该能明白的悲观锁和乐观锁

看完你就明白的锁系列之自旋锁

看完你就明白的锁系列之锁的状态

此篇文章我们来探讨一下什么是锁的公平性

锁的公平性与非公平性

我们知道,在并发环境中,多个线程需要对同一资源进行访问,同一时刻只能有一个线程能够获取到锁并进行资源访问,那么剩下的这些线程怎么办呢?这就好比食堂排队打饭的模型,最先到达食堂的人拥有最先买饭的权利,那么剩下的人就需要在第一个人后面排队,这是理想的情况,即每个人都能够买上饭。那么现实情况是,在你排队的过程中,就有个别不老实的人想走捷径,插队打饭,如果插队的这个人后面没有人制止他这种行为,他就能够顺利买上饭,如果有人制止,他就也得去队伍后面排队。

对于正常排队的人来说,没有人插队,每个人都在等待排队打饭的机会,那么这种方式对每个人来说都是公平的,先来后到嘛。这种锁也叫做公平锁。

那么假如插队的这个人成功买上饭并且在买饭的过程不管有没有人制止他,他的这种行为对正常排队的人来说都是不公平的,这在锁的世界中也叫做非公平锁。

那么我们根据上面的描述可以得出下面的结论

公平锁表示线程获取锁的顺序是按照线程加锁的顺序来分配的,即先来先得的FIFO先进先出顺序。而非公平锁就是一种获取锁的抢占机制,是随机获得锁的,和公平锁不一样的就是先来的不一定先得到锁,这个方式可能造成某些线程一直拿不到锁,结果也就是不公平的了。

锁公平性的实现

在 Java 中,我们一般通过 ReetrantLock 来实现锁的公平性

我们分别通过两个例子来讲解一下锁的公平性和非公平性

锁的公平性

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
public class MyFairLock extends Thread{

    private ReentrantLock lock = new ReentrantLock(true);
    public void fairLock(){
        try {
            lock.lock();
            System.out.println(Thread.currentThread().getName()  + "正在持有锁");
        }finally {
            System.out.println(Thread.currentThread().getName()  + "释放了锁");
            lock.unlock();
        }
    }

    public static void main(String[] args) {
        MyFairLock myFairLock = new MyFairLock();
        Runnable runnable = () -> {
            System.out.println(Thread.currentThread().getName() + "启动");
            myFairLock.fairLock();
        };
        Thread[] thread = new Thread[10];
        for(int i = 0;i < 10;i++){
            thread[i] = new Thread(runnable);
        }
        for(int i = 0;i < 10;i++){
            thread[i].start();
        }
    }
}

我们创建了一个 ReetrantLock,并给构造函数传了一个 true,我们可以查看 ReetrantLock 的构造函数

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
public ReentrantLock(boolean fair) {
  sync = fair ? new FairSync() : new NonfairSync();
}

根据 JavaDoc 的注释可知,如果是 true 的话,那么就会创建一个 ReentrantLock 的公平锁,然后并创建一个 FairSync ,FairSync 其实是一个 Sync 的内部类,它的主要作用是同步对象以获取公平锁。

而 Sync 是 ReentrantLock 中的内部类,Sync 继承 AbstractQueuedSynchronizer 类,AbstractQueuedSynchronizer 就是我们常说的 AQS ,它是 JUC(java.util.concurrent) 中最重要的一个类,通过它来实现独占锁和共享锁。

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
abstract static class Sync extends AbstractQueuedSynchronizer {...}

也就是说,我们把 fair 参数设置为 true 之后,就可以实现一个公平锁了,是这样吗?我们回到示例代码,我们可以执行一下这段代码,它的输出是顺序获取的(碍于篇幅的原因,这里就暂不贴出了),也就是说我们创建了一个公平锁

锁的非公平性

与公平性相对的就是非公平性,我们通过设置 fair 参数为 true,便实现了一个公平锁,与之相对的,我们把 fair 参数设置为 false,是不是就是非公平锁了?用事实证明一下

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
private ReentrantLock lock = new ReentrantLock(false);

其他代码不变,我们执行一下看看输出(部分输出)

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
Thread-1启动
Thread-4启动
Thread-1正在持有锁
Thread-1释放了锁
Thread-5启动
Thread-6启动
Thread-3启动
Thread-7启动
Thread-2启动

可以看到,线程的启动并没有按顺序获取,可以看出非公平锁对锁的获取是乱序的,即有一个抢占锁的过程。也就是说,我们把 fair 参数设置为 false 便实现了一个非公平锁。

公平锁的原理

接下来,我们通过 ReentrantLock 源码来讲解公平锁和非公平锁。首先先来认识一下

ReentrantLock 是什么

ReentrantLock 基本概述

ReentrantLock 是一把可重入锁,也是一把互斥锁,它具有与 synchronized 相同的方法和监视器锁的语义,但是它比 synchronized 有更多可扩展的功能。

ReentrantLock 的可重入性是指它可以由上次成功锁定但还未解锁的线程拥有。当只有一个线程尝试加锁时,该线程调用 lock() 方法会立刻返回成功并直接获取锁。如果当前线程已经拥有这把锁,这个方法会立刻返回。可以使用 isHeldByCurrentThreadgetHoldCount 进行检查。

这个类的构造函数接受可选择的 fairness 参数,当 fairness 设置为 true 时,在多线程争夺尝试加锁时,锁倾向于对等待时间最长的线程访问,这也是公平性的一种体现。否则,锁不能保证每个线程的访问顺序,也就是非公平锁。与使用默认设置的程序相比,使用许多线程访问的公平锁的程序可能会显示较低的总体吞吐量(即较慢;通常要慢得多)。但是获取锁并保证线程不会饥饿的次数比较小。无论如何请注意:锁的公平性不能保证线程调度的公平性。因此,使用公平锁的多线程之一可能会连续多次获得它,而其他活动线程没有进行且当前未持有该锁。这也是互斥性 的一种体现。

也要注意的 tryLock() 方法不支持公平性。如果锁是可以获取的,那么即使其他线程等待,它仍然能够返回成功。

推荐使用下面的代码来进行加锁和解锁

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
class MyFairLock {
  private final ReentrantLock lock = new ReentrantLock();

  public void m() {
    lock.lock();
    try {
      // ...
    } finally {
      lock.unlock()
    }
  }
}

ReentrantLock 锁通过同一线程最多支持2147483647个递归锁。尝试超过此限制会导致锁定方法引发错误。

ReentrantLock 如何实现锁公平性

我们在上面的简述中提到,ReentrantLock 是可以实现锁的公平性的,那么原理是什么呢?下面我们通过其源码来了解一下 ReentrantLock 是如何实现锁的公平性的

跟踪其源码发现,调用 Lock.lock() 方法其实是调用了 sync 的内部的方法

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
abstract void lock();

而 sync 是最基础的同步控制 Lock 的类,它有公平锁和非公平锁的实现。它继承 AbstractQueuedSynchronizer 即 使用 AQS 状态代表锁持有的数量。

lock 是抽象方法是需要被子类实现的,而继承了 AQS 的类主要有

我们可以看到,所有实现了 AQS 的类都位于 JUC 包下,主要有五类:ReentrantLockReentrantReadWriteLockSemaphoreCountDownLatchThreadPoolExecutor,其中 ReentrantLock、ReentrantReadWriteLock、Semaphore 都可以实现公平锁和非公平锁。

下面是公平锁 FairSync 的继承关系

非公平锁的NonFairSync 的继承关系

由继承图可以看到,两个类的继承关系都是相同的,我们从源码发现,公平锁和非公平锁的实现就是下面这段代码的区别(下一篇文章我们会从原理角度分析一下公平锁和非公平锁的实现)

通过上图中的源代码对比,我们可以明显的看出公平锁与非公平锁的lock()方法唯一的区别就在于公平锁在获取同步状态时多了一个限制条件:hasQueuedPredecessors()

hasQueuedPredecessors() 也是 AQS 中的方法,它主要是用来 查询是否有任何线程在等待获取锁的时间比当前线程长,也就是说每个等待线程都是在一个队列中,此方法就是判断队列中在当前线程获取锁时,是否有等待锁时间比自己还长的队列,如果当前线程之前有排队的线程,返回 true,如果当前线程位于队列的开头或队列为空,返回 false。

综上,公平锁就是通过同步队列来实现多个线程按照申请锁的顺序来获取锁,从而实现公平的特性。非公平锁加锁时不考虑排队等待问题,直接尝试获取锁,所以存在后申请却先获得锁的情况。

文章参考:

https://tech.meituan.com/2018/11/15/java-lock.html

https://www.jianshu.com/p/eaea337c5e5b

https://blog.csdn.net/oChangWen/article/details/77622889

本文参与 腾讯云自媒体同步曝光计划,分享自微信公众号。
原始发表:2019-10-29,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 Java建设者 微信公众号,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
暂无评论
推荐阅读
编辑精选文章
换一批
Java-ReentrantLock-公平锁源码分析
在了解AQS后,那应该怎么了解AQS的最佳实践那,我想再也没有Java官方的实践更加优秀了,这次我打算重新拿出系统源代码,并将其总结成一系列文章,以供将来查看.
颍川
2021/12/06
2230
不能再被问住了!ReentrantLock 源码、画图一起看一看!
" 在阅读完 JUC 包下的 AQS 源码之后,其中有很多疑问,最大的疑问就是 state 究竟是什么含义?并且 AQS 主要定义了队列的出入,但是获取资源、释放资源都是交给子类实现的,那子类是怎么实现的呢?下面开始了解 ReentrantLock。 "
程序员小航
2020/11/23
3360
不能再被问住了!ReentrantLock 源码、画图一起看一看!
面试被问公平锁和非公平锁,我竟用“排队打饭”解释得清清楚楚
有个小伙伴最近咨询我,前段时间他被面试官问了synchronized是公平锁还是非公平锁?当时就蒙圈了,最后面试结果可想而知,今天我们就用一个通俗的案例加上代码来说明公平锁和非公平锁。其实公平锁这个概念是JUC工具包才有的,比如ReentrantLock才有公平锁的概念,这篇文章我们结合生活中的实例用2段代码说明ReentrantLock公平锁和非公平锁,以及证明synchronized是非公平锁的,希望对小伙伴们有帮助。
Java程序猿阿谷
2021/01/08
7390
面试被问公平锁和非公平锁,我竟用“排队打饭”解释得清清楚楚
高并发编程-ReentrantLock公平锁深入解析
ReentrantLock是一个可重入的互斥锁,它不但具有synchronized实现的同步方法和同步代码块的基本行为和语义,而且具备很强的扩展性。ReentrantLock提供了公平锁和非公平锁两种实现,在默认情况下构造的ReentrantLock实例是非公平锁,可以在创建ReentrantLock实例的时候通过指定公平策略参数来指定是使用公平锁还是非公平锁。多线程竞争访问同一资源的时,公平锁倾向于将访问权授予等待时间最长的线程,但需要明确的是公平锁不能保证线程调度的公平性。和非公平锁相比,公平锁在多线程访问时总体吞吐量偏低,但是获得锁和保证锁分配的均衡性差异较小。本篇将基于JDK7深入源码解析公平锁的实现原理。
JavaQ
2018/09/14
9700
图解ReentrantLock底层公平锁和非公平锁实现原理
1️⃣ 公平锁:N个线程去申请锁时,会按照先后顺序进入一个队列当中去排队,依次按照先后顺序获取锁。就像下图描述的上厕所的场景一样,先来的先占用厕所,后来的只能老老实实排队。
朱季谦
2022/11/17
2.6K0
图解ReentrantLock底层公平锁和非公平锁实现原理
Java ReentrantLock锁的公平性与非公平性
锁按照公平性划分为公平锁和非公平锁,在Java中,ReentrantLock有这两种锁的具体实现,下文进行展示。
夹胡碰
2020/11/24
7940
Java ReentrantLock锁的公平性与非公平性
java架构之路(多线程)AQS之ReetrantLock显示锁的使用和底层源码解读
  说完了我们的synchronized,这次我们来说说我们的显示锁ReetrantLock。
小菜的不能再菜
2020/02/21
4680
不懂什么是锁?看看这篇你就明白了
Java 中的锁有很多,可以按照不同的功能、种类进行分类,下面是我对 Java 中一些常用锁的分类,包括一些基本的概述
cxuan
2019/12/19
4440
不懂什么是锁?看看这篇你就明白了
Java的ReentrantLock详解
ReentrantLock 是 Java 并发包(java.util.concurrent.locks)中的一个可重入锁实现,它提供了比 synchronized 关键字更灵活、功能更丰富的线程同步机制。ReentrantLock类内部总共存在Sync、NonfairSync、FairSync三个类,NonfairSync(非公平锁)与FairSync(公平锁)类继承自Sync类,Sync类继承自AbstractQueuedSynchronizer抽象类。
tt天
2024/04/23
2270
Java的ReentrantLock详解
ReetrantLock源码分析
ReentrantLock类的大部分逻辑,都是其均继承自AQS的内部类Sync实现的
Java宝典
2020/12/04
3540
理解ReentrantLock的公平锁和非公平锁
学习AQS的时候,了解到AQS依赖于内部的FIFO同步队列来完成同步状态的管理,当前线程获取同步状态失败时,同步器会将当前线程以及等待状态等信息构造成一个Node对象并将其加入到同步队列,同时会阻塞当前线程,当同步状态释放时,会把首节点中的线程唤醒,使其再次尝试获取同步状态。
后端码匠
2021/10/25
5390
Java-ReentrantLock-非公平锁源码分析
在了解AQS后,那应该怎么了解AQS的最佳实践那,我想再也没有Java官方的实践更加优秀了,这次我打算重新拿出系统源代码,并将其总结成一系列文章,以供将来查看.
颍川
2021/12/06
2810
竞技世界面试官:说一下公平锁和非公平锁的区别?
上次我们提到了乐观锁和悲观锁,那我们知道锁的类型还有很多种,我们今天简单聊一下,公平锁和非公平锁两口子,以及他们在我们代码中的实践。
Java程序猿
2021/07/19
2820
高并发编程-ReentrantLock非公平锁深入解析
ReentrantLock是一个可重入的互斥锁,它不但具有synchronized实现的同步方法和同步代码块的基本行为和语义,而且具备很强的扩展性。ReentrantLock提供了公平锁和非公平锁两种实现,在默认情况下构造的ReentrantLock实例是非公平锁,可以在创建ReentrantLock实例的时候通过指定公平策略参数来指定是使用公平锁还是非公平锁。本篇将基于JDK7深入源码解析非公平锁的实现原理。
JavaQ
2018/10/23
4960
这篇 ReentrantLock 看不懂,加我我给你发红包
在开始本篇文章的内容讲述前,先来回答我一个问题,为什么 JDK 提供一个 synchronized 关键字之后还要提供一个 Lock 锁,这不是多此一举吗?难道 JDK 设计人员都是沙雕吗?
cxuan
2020/01/17
5170
这篇 ReentrantLock 看不懂,加我我给你发红包
【原创】Java并发编程系列16 | 公平锁与非公平锁
上一篇提到重入锁 ReentrantLock 支持两种锁,公平锁与非公平锁。那么这篇文章就来介绍一下公平锁与非公平锁。
java进阶架构师
2020/03/19
7420
【原创】Java并发编程系列16 | 公平锁与非公平锁
啥?小胖连公平锁 & 非公平锁都不知道?真的菜!
来到多线程的第十二篇,前十一篇请点文末底部的上、下一篇标签,这篇说说什么是公平锁 & 非公平锁?开篇之前,先聊聊它们的定义以及优缺点。
JavaFish
2020/12/17
5311
啥?小胖连公平锁 & 非公平锁都不知道?真的菜!
面试被问ReentrantLock的公平锁与非公平锁
面试被问ReentrantLock的公平锁与非公平锁的区别以及实现。 建议先阅读Java中的锁原理、锁优化、CAS、AQS,看这篇就对了!
田维常
2020/05/14
6430
公平锁与非公平锁的深入剖析与Java实现
今日推荐文章:当 Semaphore 遇到 finally,有大坑,要注意!-腾讯云开发者社区-腾讯云
小马哥学JAVA
2024/11/26
1340
深入理解独占锁ReentrantLock类锁
【1】ReentrantLock是一种基于AQS框架的应用实现,是JDK中的一种线程并发访问的同步手段,它的功能类似于synchronized是一种互斥锁,可以保证线程安全。
忧愁的chafry
2022/10/30
3900
推荐阅读
相关推荐
Java-ReentrantLock-公平锁源码分析
更多 >
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档
本文部分代码块支持一键运行,欢迎体验
本文部分代码块支持一键运行,欢迎体验