JDK源码解析之AbstractQueuedSynchronizer(下)

简介: JDK源码解析之AbstractQueuedSynchronizer(下)

4.3.5.3 数据结构

前驱节点
  • 链接到当前节点/线程所依赖的用来检查 waitStatus 的前驱节点
  • image.png
  • 在入队期间赋值,并且仅在出队时将其清空(为了GC)。


此外,在取消一个前驱结点后,在找到一个未取消的节点后会短路,这将始终存在,因为头节点永远不会被取消:只有成功 acquire 后,一个节点才会变为头。


取消的线程永远不会成功获取,并且线程只会取消自身,不会取消任何其他节点。

后继节点

链接到后继节点,当前节点/线程在释放时将其unpark。 在入队时赋值,在绕过已取消的前驱节点时进行调整,在出队时置null(为了GC)。

入队操作直到附加后才赋值前驱节点的next字段,因此看到next字段为 null,并不一定意味该节点位于队尾(有时间间隙)。

但若next == null,则可从队尾开始扫描prev以进行再次检查。

// 若节点通过从tail向前搜索发现在在同步队列上,则返回 true
// 仅在调用了 isOnSyncQueue 且有需要时才调用
private boolean findNodeFromTail(Node node) {
    Node t = tail;
    for (;;) {
        if (t == node)
            return true;
        if (t == null)
            return false;
        t = t.prev;
    }
}
final boolean isOnSyncQueue(Node node) {
    if (node.waitStatus == Node.CONDITION || node.prev == null)
        return false;
    if (node.next != null) // If has successor, it must be on queue
        return true;
    /**
     * node.prev 可以非null,但还没有在队列中,因为将它放在队列中的 CAS 可能会失败。
     * 所以必须从队尾向前遍历以确保它确实成功了。
     * 在调用此方法时,它将始终靠近tail,并且除非 CAS 失败(这不太可能)
     * 否则它会在那里,因此几乎不会遍历太多
     */    
    return findNodeFromTail(node);
}

已取消节点的next字段设置为指向节点本身而不是null,以使isOnSyncQueue更轻松。

image.png

使该节点入队的线程。 在构造时初始化,使用后消亡。

image.png

在同步队列中,nextWaiter 表示当前节点是独占模式还是共享模式

在条件队列中,nextWaiter 表示下一个节点元素


链接到在条件队列等待的下一个节点,或者链接到特殊值SHARED。 由于条件队列仅在以独占模式保存时才被访问,因此我们只需要一个简单的链接队列即可在节点等待条件时保存节点。 然后将它们转移到队列中以重新获取。 并且由于条件只能是独占的,因此我们使用特殊值来表示共享模式来保存字段。

image.png

5 Condition 接口

JDK5 时提供。

  • 条件队列 ConditionObject 实现了 Condition 接口
  • image.png
  • 本节就让我们一起来研究之
  • 2.png
  • Condition 将对象监视方法(wait,notify和notifyAll)分解为不同的对象,从而通过与任意Lock实现结合使用,从而使每个对象具有多个wait-sets。 当 Lock 替换了 synchronized 方法和语句的使用,Condition 就可以替换了Object监视器方法的使用。


Condition 的实现可以提供与 Object 监视方法不同的行为和语义,例如保证通知的顺序,或者在执行通知时不需要保持锁定。 如果实现提供了这种专门的语义,则实现必须记录这些语义。


Condition实例只是普通对象,它们本身可以用作 synchronized 语句中的目标,并且可以调用自己的监视器 wait 和 notification 方法。 获取 Condition 实例的监视器锁或使用其监视器方法与获取与该条件相关联的锁或使用其 await 和 signal 方法没有特定的关系。 建议避免混淆,除非可能在自己的实现中,否则不要以这种方式使用 Condition 实例。

 class BoundedBuffer {
   final Lock lock = new ReentrantLock();
   final Condition notFull  = lock.newCondition(); 
   final Condition notEmpty = lock.newCondition(); 
   final Object[] items = new Object[100];
   int putptr, takeptr, count;
   public void put(Object x) throws InterruptedException {
     lock.lock();
     try {
       while (count == items.length)
         notFull.await();
       items[putptr] = x;
       if (++putptr == items.length) putptr = 0;
       ++count;
       notEmpty.signal();
     } finally {
       lock.unlock();
     }
   }
   public Object take() throws InterruptedException {
     lock.lock();
     try {
       while (count == 0)
         notEmpty.await();
       Object x = items[takeptr];
       if (++takeptr == items.length) takeptr = 0;
       --count;
       notFull.signal();
       return x;
     } finally {
       lock.unlock();
     }
   }
 }

(ArrayBlockingQueue类提供了此功能,因此没有理由实现此示例用法类。)

定义出一些方法,这些方法奠定了条件队列的基础

API

await

  • 使当前线程等待,直到被 signalled 或被中断
  • image.png
  • 与此 Condition 相关联的锁被原子释放,并且出于线程调度目的,当前线程被禁用,并且处于休眠状态,直到发生以下四种情况之一:


其它线程为此 Condition 调用了 signal 方法,并且当前线程恰好被选择为要唤醒的线程

其它线程为此 Condition 调用了 signalAll 方法

其它线程中断了当前线程,并且当前线程支持被中断

发生“虚假唤醒”。

在所有情况下,在此方法可以返回之前,必须重新获取与此 Condition 关联的锁,才能真正被唤醒。当线程返回时,可以保证保持此锁。

await 超时时间

  • 使当前线程等待,直到被 signal 或中断,或经过指定的等待时间
  • image.png
  • 此方法在行为上等效于:
awaitNanos(unit.toNanos(time)) > 0

所以,虽然入参可以是任意单位的时间,但其实仍会转化成纳秒

awaitNanos

image.png

注意这里选择纳秒是为了避免计算剩余等待时间时的截断误差

signal()

  • 唤醒条件队列中的一个线程,在被唤醒前必须先获得锁
  • image.png

signalAll()

  • 唤醒条件队列中的所有线程
  • image.png
目录
相关文章
|
3天前
|
Java Android开发
Android12 双击power键启动相机源码解析
Android12 双击power键启动相机源码解析
12 0
|
3天前
|
分布式计算 Java API
Java8 Lambda实现源码解析
Java8的lambda应该大家都比较熟悉了,本文主要从源码层面探讨一下lambda的设计和实现。
|
4天前
|
缓存 自然语言处理 JavaScript
万字长文深度解析JDK序列化原理及Fury高度兼容的极致性能实现
Fury是一个基于JIT动态编译的高性能多语言原生序列化框架,支持Java/Python/Golang/C++/JavaScript等语言,提供全自动的对象多语言/跨语言序列化能力,以及相比于别的框架最高20~200倍的性能。
|
4天前
|
算法 Java Go
ArrayList源码解析
ArrayList源码解析
9 1
|
4天前
|
存储 安全 Java
【HashMap源码解析(一)(佬你不来看看?)】
【HashMap源码解析(一)(佬你不来看看?)】
10 1
|
11天前
|
缓存 Java 开发者
10个点介绍SpringBoot3工作流程与核心组件源码解析
Spring Boot 是Java开发中100%会使用到的框架,开发者不仅要熟练使用,对其中的核心源码也要了解,正所谓知其然知其所以然,V 哥建议小伙伴们在学习的过程中,一定要去研读一下源码,这有助于你在开发中游刃有余。欢迎一起交流学习心得,一起成长。
|
15天前
|
SQL 缓存 Java
|
15天前
|
XML 人工智能 Java
Spring Bean名称生成规则(含源码解析、自定义Spring Bean名称方式)
Spring Bean名称生成规则(含源码解析、自定义Spring Bean名称方式)
|
17天前
|
安全 Java Shell
Android13 adb input 调试命令使用和源码解析
Android13 adb input 调试命令使用和源码解析
27 0
Mixtral MOE 部分源码解析
Mixtral MOE 部分源码解析
21 0

推荐镜像

更多