JAVA为简化开发者开发提供了很多并发的工具,包括各种同步器,有了JDK我们只要学会简单使用类API即可。但这并不意味着不需要探索其具体的实现机制,本文从JDK源码角度简单讲讲并发时线程竞争的公平性。

所谓公平性指所有线程对临界资源申请访问权限的成功率都一样,不会让某些线程拥有优先权。我们知道CLH Node FIFO等待队列是一个先进先出的队列,那么是否就可以说每条线程获取锁时就是公平的呢?关于公平性这里分拆成三个点分别阐述:

① 准备入队列的节点,此情况讨论的是线程加入等待队列时产生的竞争是否公平,线程在尝试获取锁失败后将被加入等待队列,这时多个线程通过自旋将节点加入队列,所有线程在自旋过程中是无法保证其公平性的,可能后来的线程比早到的先进入队列,所以节点入队列不具公平性。
        ② 等待队列中的节点,情况①中成功加入队列后即成为等待队列中的节点,我们知道此队列是一个先入先出队列,那么很简单能得到,队列中的所有节点是公平的,他们都按照顺序等待自己被前驱节点唤醒并获取锁,所以等待队列中的节点具有公平性。
        ③ 闯入的节点,这种情况是指一个新线程到达共享资源边界时不管等待队列中是否存在其他等待节点它都将优先尝试去获取锁,这种称为可闯入策略。可闯入特性破坏了公平性,JDK的AQS对外体现的公平性主要由此体现,下面将对闯入特性展开分析。
        AQS提供的基础获取锁算法是一种可闯入的算法,即如果有新线程到来先进行一次获取尝试,不成功的情况下才将当前线程加入等待队列。如图2-5-9-6所示,等待队列中节点线程按照顺序一个接一个尝试去获取共享资源的使用权,某时刻头结点线程准备尝试获取的同时另外一条线程闯入,此线程并非直接加入等待队列的尾部,而是先跟头结点线程竞争获取资源,闯入线程如果成功获取共享资源则直接执行,头结点线程则继续等待下一次尝试,如此一来闯入线程成功插队,后来的线程比早到的线程先执行,说明AQS基础获取算法是不严格公平的。

 
图2-5-9-6 闯入线程

基础获取算法逻辑简化如下:首先尝试获取锁,假如获取失败才创建节点并加入到等待队列的尾部,接着通过不断循环检查是否轮到自己执行,当然此过程为了提高性能可能将线程先挂起,最终由前驱节点唤醒。
if(尝试获取锁失败) {
    创建node
    使用CAS方式把node插入到队列尾部
    while(true){
    if(尝试获取锁成功 并且 node的前驱节点为头节点){
把当前节点设置为头节点
    跳出循环
}else{
    使用CAS方式修改node前驱节点的waitStatus标识为signal
    if(修改成功)
        挂起当前线程 
}
}
        为什么要使用闯入策略?可闯入的策略通常可以提供更高的总吞吐量。由于一般同步器颗粒度比较小,也可以说共享资源的范围较小,而线程从阻塞状态到被唤醒所消耗的时间周期可能是通过共享资源时间周期的几倍甚至几十倍,如此一来线程唤醒过程中将存在一个很大的时间周期空窗期,导致资源没有得到充分利用,为了提高吞吐量,引入这种闯入策略,它可以使在等待队列头结点从阻塞到被唤醒的时间段内闯入的线程直接获取锁并通过同步器,以便充分利用唤醒过程这一空窗期,大大增加了吞吐率。另外,闯入机制的实现对外提供一种竞争调节机制,即开发者可以在自定义同步器中定义闯入尝试获取的次数,假设次数为n则不断重复获取直到n次都获取不成功才把线程加入等待队列中,随着次数n的增加可以增大成功闯入的几率。同时,这种闯入策略可能导致等待队列中的线程饥饿,因为锁可能一直被闯入的线程获取,但由于一般持有同步器的时间很短暂而避免饥饿的发生,反之如果保护的代码体很长并且持有同步器的时间较长,这将大大增加等待队列无限等待的风险。

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

喜欢研究java的同学可以交个朋友,下面是本人的微信号:

从JDK源码角度看java并发的公平性的更多相关文章

  1. 从JDK源码角度看java并发的原子性如何保证

    JDK源码中,在研究AQS框架时,会发现很多地方都使用了CAS操作,在并发实现中CAS操作必须具备原子性,而且是硬件级别的原子性,java被隔离在硬件之上,明显力不从心,这时为了能直接操作操作系统层面 ...

  2. 从JDK源码角度看java并发线程的中断

    线程的定义给我们提供了并发执行多个任务的方式,大多数情况下我们会让每个任务都自行执行结束,这样能保证事务的一致性,但是有时我们希望在任务执行中取消任务,使线程停止.在java中要让线程安全.快速.可靠 ...

  3. 从JDK源码角度看Short

    概况 Java的Short类主要的作用就是对基本类型short进行封装,提供了一些处理short类型的方法,比如short到String类型的转换方法或String类型到short类型的转换方法,当然 ...

  4. 从JDK源码角度看Byte

    Java的Byte类主要的作用就是对基本类型byte进行封装,提供了一些处理byte类型的方法,比如byte到String类型的转换方法或String类型到byte类型的转换方法,当然也包含与其他类型 ...

  5. 从JDK源码角度看Object

    Java的Object是所有其他类的父类,从继承的层次来看它就是最顶层根,所以它也是唯一一个没有父类的类.它包含了对象常用的一些方法,比如getClass.hashCode.equals.clone. ...

  6. 从JDK源码角度看Boolean

    Java的Boolean类主要作用就是对基本类型boolean进行封装,提供了一些处理boolean类型的方法,比如String类型和boolean类型的转换. 主要实现源码如下: public fi ...

  7. 从JDK源码角度看并发竞争的超时

    JDK中的并发框架提供的另外一个优秀机制是锁获取超时的支持,当大量线程对某一锁竞争时可能导致某些线程在很长一段时间都获取不了锁,在某些场景下可能希望如果线程在一段时间内不能成功获取锁就取消对该锁的等待 ...

  8. 从JDK源码角度看并发锁的优化

    在CLH锁核心思想的影响下,JDK并发包以CLH锁作为基础而设计,其中主要是考虑到CLH锁更容易实现取消与超时功能.比起原来的CLH锁已经做了很大的改造,主要从两方面进行了改造:节点的结构与节点等待机 ...

  9. 从JDK源码角度看线程池原理

    "池"技术对我们来说是非常熟悉的一个概念,它的引入是为了在某些场景下提高系统某些关键节点性能,最典型的例子就是数据库连接池,JDBC是一种服务供应接口(SPI),具体的数据库连接实 ...

随机推荐

  1. 正则替换replace中$1的用法以及常用正则

    一.repalce定义 用于在字符串中用一些字符替换另一些字符,或替换一个与正则表达式匹配的子串. stringObject.replace(regexp/substr,replacement)参数一 ...

  2. 在Linux系统上获取命令帮助信息和划分man文档

    使用历史命令history 打完以后前面会有顺序号的比如1 cd2 ls3 pwd如果需要重新执行cd命令则可以执行 !3 命令 命令补全功能 比如你要执行history命令 可以打上histo+键 ...

  3. R语言中函数调试

    有时候会用R语言写一下简单的脚本处理函数,加入需要调试的话可以按照下面的步骤进行: fun <- function(x , y){ x + y x - y x * y x / y } debug ...

  4. UIkit复习:UIContorl及子控件的剖析

    1.模块继承关系: 1.UIButton        ->UIControl  -> UIView 2.UILabel          ->UIview 3.UIImageVie ...

  5. 安卓 LayoutInflater参数作用

    方法重载1 public View inflate (int resource, ViewGroup root, boolean attachToRoot) 方法重载2 public View inf ...

  6. GitHub Android Librarys Top 100 简介

    GitHub Android Librarys Top 100 简介 本项目主要对目前 GitHub 上排名前 100 的 Android 开源库进行简单的介绍, 至于排名完全是根据GitHub搜索J ...

  7. Compile C++ code in Matlab with OpenCV support

    Provides a function named as "mex_opencv(src)" The code function mex_opencv(src) ARC = 'x6 ...

  8. Swift3中dispatch_once废弃的解决办法

    在Swift中如果想搞类的单例模式,那么在初始化的时候一般会使用just one time执行的方式,我们使用dispatch_once_t配合调用dispatch_once方法,一般的代码如下: s ...

  9. python模块:时间处理模块

    http://blog.csdn.net/pipisorry/article/details/53067168 常用python自带时间处理模块 python自带的时间处理模块参考[操作系统服务:ti ...

  10. J2EE进阶(十八)基于留言板分析SSH工作流程

    J2EE进阶(十八)基于留言板分析SSH工作流程   留言板采用SSH(Struts1.2 + Spring3.0 + Hibernate3.0)架构.   工作流程(以用户登录为例):   首先是用 ...