Java 并发编程:AQS 的公平性

Wesley13
• 阅读 639

所谓公平是指所有线程对临界资源申请访问权限的成功率都一样,它不会让某些线程拥有优先权。通过几篇文章的分析我们知道了JDK的AQS的锁是基于CLH锁进行优化的,而其中使用了FIFO队列,也就是说等待队列是一个先进先出的队列。那是否就可以说每条线程获取锁时就是公平的呢?关于公平性,严格来说应该分成三个点来看:入队阶段、唤醒阶段以及闯入策略。

友情链接:

入队阶段

Java 并发编程:AQS 的公平性

唤醒阶段

当线程节点成功加入等待队列后便成为等待队列中的节点,而且这是一个先入先出队列,那么我们可以得到一个结论:队列中的所有节点是公平的。因为等待队列中的所有节点都按照顺序等待自己被前驱节点唤醒并获取锁,所以等待队列中的节点具有公平性。

Java 并发编程:AQS 的公平性

闯入策略

闯入策略是AQS框架为了提升性能而设计的一个策略,具体是指一个新线程到达共享资源边界时不管等待队列中是否存在其它等待节点,新线程都将优先尝试去获取锁,这看起来就像是闯入行为。闯入策略破坏了公平性,AQS框架对外体现的公平性主要也由此体现。

AQS提供的锁获取操作运用了可闯入算法,即如果有新线程到来先进行一次获取尝试,不成功的情况下才将当前线程加入等待队列。如下图,等待队列中节点线程按照顺序一个接一个尝试去获取共享资源的使用权。而某一时刻头结点线程准备尝试获取的同时另外一条线程闯入,新线程并非直接加入等待队列的尾部,而是先跟头结点线程竞争获取资源。闯入线程如果成功获取共享资源则直接执行,头结点线程则继续等待下一次尝试。如此一来闯入线程成功插队,后来的线程比早到的线程先执行,说明AQS锁获取算法是不严格公平的。

Java 并发编程:AQS 的公平性

闯入逻辑

下图是包含了闯入策略的锁获取算法伪代码,我们主要关注红色方框的逻辑。它会优先直接去尝试获取锁,如果获取失败(即闯入失败)才创建节点并加入到等待队列的尾部。

Java 并发编程:AQS 的公平性

为什么需要闯入策略

为什么要使用闯入策略呢?闯入策略通常可以提升总吞吐量。由于一般同步器颗粒度比较小,也可以说共享资源的范围较小,而线程从阻塞状态到被唤醒所消耗的时间周期可能是通过共享资源时间周期的几倍甚至几十倍。

如此一来线程唤醒过程中将存在一个很大的时间周期空窗期,导致资源没有得到充分利用,同时如果每个线程都先入队再唤醒的话也会导致效率低下。为了避免没必要的线程挂起和唤醒,也为了提高吞吐量,于是引入这种闯入策略。它可以充分利用阻塞唤醒空窗期,也避免了无谓的挂起和唤醒操作,从而大大增加了吞吐率。

闯入机制的实现对外提供一种竞争调节机制,开发者可以在自定义同步器中定义闯入尝试获取的次数。假设次数为n则不断重复获取直到n次都获取不成功才把线程加入等待队列中,随着次数n的增加可以增大成功闯入的几率。  同时,这种闯入策略可能导致等待队列中的线程饥饿,因为锁可能一直被闯入的线程获取。但由于一般持有同步器的时间很短暂所以能避免饥饿的发生,反之如果持有锁的时间较长,则将大大增加等待队列无限等待的风险。

总结

实际情况中我们要根据需求制定策略,在一个公平性要求很高的场景,则可以把闯入策略去除掉以达到公平。在自定义同步器中可以通过AQS预留方法tryAcquire方法实现,只需判断当前线程是否为等待队列中头结点对应的线程即可。若不是则直接返回false,尝试获取失败。但这种公平性是相对于Java语义层面上的公平性,在现实中JVM的实现可能也会直接影响线程执行的顺序。

Java 并发编程:AQS 的公平性

点赞
收藏
评论区
推荐文章
blmius blmius
3年前
MySQL:[Err] 1292 - Incorrect datetime value: ‘0000-00-00 00:00:00‘ for column ‘CREATE_TIME‘ at row 1
文章目录问题用navicat导入数据时,报错:原因这是因为当前的MySQL不支持datetime为0的情况。解决修改sql\mode:sql\mode:SQLMode定义了MySQL应支持的SQL语法、数据校验等,这样可以更容易地在不同的环境中使用MySQL。全局s
皕杰报表之UUID
​在我们用皕杰报表工具设计填报报表时,如何在新增行里自动增加id呢?能新增整数排序id吗?目前可以在新增行里自动增加id,但只能用uuid函数增加UUID编码,不能新增整数排序id。uuid函数说明:获取一个UUID,可以在填报表中用来创建数据ID语法:uuid()或uuid(sep)参数说明:sep布尔值,生成的uuid中是否包含分隔符'',缺省为
待兔 待兔
5个月前
手写Java HashMap源码
HashMap的使用教程HashMap的使用教程HashMap的使用教程HashMap的使用教程HashMap的使用教程22
Wesley13 Wesley13
3年前
java中的锁
记录一下公平锁,非公平锁,可重入锁(递归锁),读写锁,自旋锁的概念,以及一些和锁有关的java类。公平锁与非公平锁:公平锁就是在多线程环境下,每个线程在获取锁时,先查看这个锁维护的队列,如果队列为空或者自身就是等待队列的第一个,就占有锁。否则就加入到等待队列中,按照FIFO的顺序依次占有锁。非公平锁会一上来就试图占
Wesley13 Wesley13
3年前
Java日期时间API系列31
  时间戳是指格林威治时间1970年01月01日00时00分00秒起至现在的总毫秒数,是所有时间的基础,其他时间可以通过时间戳转换得到。Java中本来已经有相关获取时间戳的方法,Java8后增加新的类Instant等专用于处理时间戳问题。 1获取时间戳的方法和性能对比1.1获取时间戳方法Java8以前
Wesley13 Wesley13
3年前
Java并发包小结
1、Lock  Lock功能对应关键字synchrozied功能,lock和unlock方法用于加锁和释放锁。等待锁的线程加入到等待链表中,同时阻塞线程,锁释放时,从等待链表中取出等待的线程执行,取等待的线程分公平与非公平两种方式,公平方式取第一个等待的线程,非公平方式当前正在获取锁的线程可能立刻执行,而不用加入到等待队列中,排队执行。2、Con
Stella981 Stella981
3年前
Android So动态加载 优雅实现与原理分析
背景:漫品Android客户端集成适配转换功能(基于目标识别(So库35M)和人脸识别库(5M)),导致apk体积50M左右,为优化客户端体验,决定实现So文件动态加载.!(https://oscimg.oschina.net/oscnet/00d1ff90e4b34869664fef59e3ec3fdd20b.png)点击上方“蓝字”关注我
Wesley13 Wesley13
3年前
AQS之工作原理
前面一章LZ简单的介绍了下AbstractQueuedSynchronizer(AQS)以及AQS中提供的一些模板方法和作用,这一章LZ将用一个简单的实例来介绍下AQS中独占锁的工作原理。独占锁顾名思义就是在同一时刻只能有一个线程能获取到锁,而其它需要获取这把锁的线程将进入到同步队列中等待获取到了锁的线程释放这把锁,只有获取锁的线程释放了锁,同步队列中的线程
Wesley13 Wesley13
3年前
Java并发编程:AQS的公平性
所谓公平是指所有线程对临界资源申请访问权限的成功率都一样,它不会让某些线程拥有优先权。通过几篇文章的分析我们知道了JDK的AQS的锁是基于CLH锁进行优化的,而其中使用了FIFO队列,也就是说等待队列是一个先进先出的队列。那是否就可以说每条线程获取锁时就是公平的呢?关于公平性,严格来说应该分成三个点来看:入队阶段、唤醒阶段以及闯入策略。友情链接:
Wesley13 Wesley13
3年前
35岁是技术人的天花板吗?
35岁是技术人的天花板吗?我非常不认同“35岁现象”,人类没有那么脆弱,人类的智力不会说是35岁之后就停止发展,更不是说35岁之后就没有机会了。马云35岁还在教书,任正非35岁还在工厂上班。为什么技术人员到35岁就应该退役了呢?所以35岁根本就不是一个问题,我今年已经37岁了,我发现我才刚刚找到自己的节奏,刚刚上路。