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

        JDK中的并发框架提供的另外一个优秀机制是锁获取超时的支持,当大量线程对某一锁竞争时可能导致某些线程在很长一段时间都获取不了锁,在某些场景下可能希望如果线程在一段时间内不能成功获取锁就取消对该锁的等待以提高性能,这时就需要用到超时机制。在JDK1.5之前并没有对此支持,当时的并发控制职能通过JVM内置的synchronized关键词实现锁,但对一些特殊要求却力不从心,例如超时取消控制。JDK1.5开始引入并发工具完美解决了此问题,JDK对并发线程开始提供超时的支持。
        为了更精确地保证时间间隔统计的准确性,实现时使用了System.nanoTime()更为精确的方法,它能精确到纳秒级别。超时机制的思想就是在不断进行锁竞争的同时记录竞争的时间,一旦时间段超过指定的时间则停止轮询直接返回,返回前对等待队列中对应节点进行取消操作。往下看实现的逻辑,

if(尝试获取锁失败) {
long lastTime = System.nanoTime();
    创建node
    使用CAS方式把node插入到队列尾部
    while(true){
    if(尝试获取锁成功 并且 node的前驱节点为头节点){
把当前节点设置为头节点
    跳出循环
}else{
    if (nanosTimeout <= 0){
取消等待队列中此节点
跳出循环
}
    使用CAS方式修改node前驱节点的waitStatus标识为signal
    if(修改成功)
        if(nanosTimeout > spinForTimeoutThreshold)
        阻塞当前线程nanosTimeout纳秒
    long now = System.nanoTime();
    nanosTimeout -= now - lastTime;
    lastTime = now;
}
}

        上面正在锁的获取逻辑中添加超时处理,核心逻辑是不断循环减去处理的时间消耗,一旦小于0就取消节点并跳出循环,其中有两点必须要注意,一个是真正的阻塞时间应该是扣除了竞争入队的时间后剩余的时间,保证阻塞事件的准确性,我们可以看到每次循环都会减去相应的处理时间;另外一个是关于spinForTimeoutThreshold变量阀值,它是决定使用自旋方式消耗时间还是使用系统阻塞方式消耗时间的分割线,JDK并发工具包作者通过测试将默认值设置为1000ns,即如果在成功插入等待队列后剩余时间大于1000ns则调用系统底层阻塞,否则不调用系统底层,取而代之的是仅仅让之在Java应用层不断循环消耗时间,属于优化的措施。

        至此,JDK实现在获取锁的过程中提供了超时机制,超时的支持让Java在并发方面提供了更完善的机制,更多的并发策略满足开发者更多需求。

====广告时间,可直接跳过====

鄙人的新书《Tomcat内核设计剖析》已经在京东预售了,有需要的朋友可以到https://item.jd.com/12185360.html 进行预定。感谢各位朋友。

=========================

欢迎关注:

时间: 2024-09-15 11:26:31

从JDK源码角度看并发竞争的超时的相关文章

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

        在CLH锁核心思想的影响下,JDK并发包以CLH锁作为基础而设计,其中主要是考虑到CLH锁更容易实现取消与超时功能.比起原来的CLH锁已经做了很大的改造,主要从两方面进行了改造:节点的结构与节点等待机制.在结构上引入了头结点和尾节点,他们分别指向队列的头和尾,尝试获取锁.入队列.释放锁等实现都与头尾节点相关,并且每个节点都引入前驱节点和后后续节点的引用:在等待机制上由原来的自旋改成阻塞唤醒.如图,通过前驱后续节点的引用一节节连接起来形成一个链表队列,对于头尾节点的更新必须是原子的

从JDK源码角度看Float

关于IEEE 754 在看Float前需要先了解IEEE 754标准,该标准定义了浮点数的格式还有一些特殊值,它规定了计算机中二进制与十进制浮点数转换的格式及方法.规定了四种表示浮点数值的方法,单精确度(32位).双精确度(64位).延伸单精确度(43位以上)与延伸双精确度(79位以上).多数编程语言支持单精确度和双精确度,这里讨论的Float就是Java的单精确度的实现. 浮点数的表示 浮点数由三部分组成,如下图,符号位s.指数e和尾数f. 对于求值我们是有一个公式对应的,根据该公式来看会更简

从JDK源码角度看Byte

Java的Byte类主要的作用就是对基本类型byte进行封装,提供了一些处理byte类型的方法,比如byte到String类型的转换方法或String类型到byte类型的转换方法,当然也包含与其他类型之间的转换方法. 主要实现代码如下: public final class Byte extends Number implements Comparable<Byte> { public static final byte MIN_VALUE = -128; public static fina

从JDK源码角度看Long

概况 Java的Long类主要的作用就是对基本类型long进行封装,提供了一些处理long类型的方法,比如long到String类型的转换方法或String类型到long类型的转换方法,当然也包含与其他类型之间的转换方法.除此之外还有一些位相关的操作. 继承结构 --java.lang.Object --java.lang.Number --java.lang.Long 主要属性 public static final long MIN_VALUE = 0x8000000000000000L;

从JDK源码角度看Integer

概况 Java的Integer类主要的作用就是对基本类型int进行封装,提供了一些处理int类型的方法,比如int到String类型的转换方法或String类型到int类型的转换方法,当然也包含与其他类型之间的转换方法.除此之外还有一些位相关的操作. 继承结构 --java.lang.Object --java.lang.Number --java.lang.Integer 主要属性 第一部分 public static final int MIN_VALUE = 0x80000000; pub

从JDK源码角度看Short

概况 Java的Short类主要的作用就是对基本类型short进行封装,提供了一些处理short类型的方法,比如short到String类型的转换方法或String类型到short类型的转换方法,当然也包含与其他类型之间的转换方法. 继承结构 --java.lang.Object --java.lang.Number --java.lang.Short 主要属性 public static final short MIN_VALUE = -32768; public static final s

从JDK源码角度看Boolean

Java的Boolean类主要作用就是对基本类型boolean进行封装,提供了一些处理boolean类型的方法,比如String类型和boolean类型的转换. 主要实现源码如下: public final class Boolean implements java.io.Serializable, Comparable<Boolean> { private final boolean value; public static final Boolean TRUE = new Boolean(

从JDK源码角度看java并发的公平性

        JAVA为简化开发者开发提供了很多并发的工具,包括各种同步器,有了JDK我们只要学会简单使用类API即可.但这并不意味着不需要探索其具体的实现机制,本文从JDK源码角度简单讲讲并发时线程竞争的公平性.         所谓公平性指所有线程对临界资源申请访问权限的成功率都一样,不会让某些线程拥有优先权.我们知道CLH Node FIFO等待队列是一个先进先出的队列,那么是否就可以说每条线程获取锁时就是公平的呢?关于公平性这里分拆成三个点分别阐述:         ① 准备入队列的节

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

        "池"技术对我们来说是非常熟悉的一个概念,它的引入是为了在某些场景下提高系统某些关键节点性能,最典型的例子就是数据库连接池,JDBC是一种服务供应接口(SPI),具体的数据库连接实现类由不同厂商实现,数据库连接的建立和销毁都是很耗时耗资源的操作,为了查询数据库中某条记录,最原始的一个过程是建立连接.发送查询语句.返回查询结果.销毁连接,假如仅仅是一个很简单的查询语句,那么可能建立连接与销毁连接两个步骤就已经占所有资源时间消耗的绝大部分,如此低下的效率显然让人无法接受.针