皮皮网

【股票配置 源码】【spring源码网】【套料实例源码】blockingqueue实现源码

来源:bmob社区类源码 时间:2024-12-22 23:25:41

1.LinkedBlockingQueue
2.Java并发编程笔记之LinkedBlockingQueue源码探究
3.还不了解Java的现源5大BlockingQueue阻塞队列源码,看这篇文章就够了
4.java阻塞队列的现源操作方法有哪些?
5.从源码全面解析 LinkedBlockingQueue的来龙去脉
6.阿里面试官:你了解过延迟队列DelayQueue的底层实现原理吗?

blockingqueue实现源码

LinkedBlockingQueue

        LinkedBlockingDeque在结构上有别于之前讲解过的阻塞队列,它不是Queue而是Deque,中文翻译成双端队列,双端队列指可以从任意一端入队或者出队元素的队列,实现了在队列头和队列尾的高效插入和移除

        LinkedBlockingDeque是链表实现的线程安全的无界的同时支持FIFO、LIFO的双端阻塞队列,可以回顾下之前的LinkedBlockingQueue阻塞队列特点,本质上是类似的,但是又有些不同:

        Queue和Deque的关系有点类似于单链表和双向链表,LinkedBlockingQueue和LinkedBlockingDeque的内部结点实现就是单链表和双向链表的区别,具体可参考源码。

        在第二点中可能有些人有些疑问,两个互斥锁和一个互斥锁的区别在哪里?我们可以考虑以下场景:

        A线程先进行入队操作,B线程随后进行出队操作,如果是LinkedBlockingQueue,A线程入队过程还未结束(已获得锁还未释放),B线程出队操作不会被阻塞等待(锁不同),如果是LinkedBlockingDeque则B线程会被阻塞等待(同一把锁)A线程完成操作才继续执行

        LinkedBlockingQueue一般的操作是获取一把锁就可以,但有些操作例如remove操作,则需要同时获取两把锁,之前的LinkedBlockingQueue讲解曾经说明过

        LinkedBlockingQueue 由于是单链表结构,只能一端操作,读只能在头,写只能在尾,因此两把锁效率更高。LinkedBlockingDeque 由于是双链表结构,两端头尾都能读写,因此只能用一把锁保证原子性。 当然效率也就更低

        ArrayBlockingQueue

        LinkedBlockingQueue

        问题,为什么ArrayBlockingQueue 不能用两把锁

        因为取出后,ArrayBlockingQueue 的元素需要向前移动。

        LinkedBlockingQueue内部由单链表实现,只能从head取元素,从tail添加元素。添加元素和获取元素都有独立的锁,也就是说LinkedBlockingQueue是读写分离的,读写操作可以并行执行。LinkedBlockingQueue采用可重入锁(ReentrantLock)来保证在并发情况下的线程安全。

        LinkedBlockingQueue一共有三个构造器,分别是无参构造器、可以指定容量的构造器、可以穿入一个容器的构造器。如果在创建实例的时候调用的是无参构造器,LinkedBlockingQueue的默认容量是Integer.MAX_VALUE,这样做很可能会导致队列还没有满,但是内存却已经满了的情况(内存溢出)。

        size()方法会遍历整个队列,时间复杂度为O(n),所以最好选用isEmtpy

        1.判断元素是否为null,为null抛出异常

        2.加锁(可中断锁)

        3.判断队列长度是否到达容量,如果到达一直等待

        4.如果没有队满,enqueue()在队尾加入元素

        5.队列长度加1,此时如果队列还没有满,调用signal唤醒其他堵塞队列

        1.加锁(依旧是ReentrantLock),注意这里的锁和写入是不同的两把锁

        2.判断队列是否为空,如果为空就一直等待

        3.通过dequeue方法取得数据

        3.取走元素后队列是否为空,如果不为空唤醒其他等待中的队列

        原理:在队尾插入一个元素, 如果队列没满,立即返回true; 如果队列满了,立即返回false。

        原理:如果没有元素,直接返回null;如果有元素,出队

        1、具体入队与出队的原理图:

        图中每一个节点前半部分表示封装的数据x,后边的表示指向的下一个引用。

        1.1、初始化

        初始化之后,初始化一个数据为null,且head和last节点都是这个节点。

        1.2、入队两个元素过后

        1.3、出队一个元素后

        表面上看,只是将头节点的next指针指向了要删除的x1.next,事实上这样我觉的就完全可以,但是jdk实际上是将原来的head节点删除了,而上边看到的这个head节点,正是刚刚出队的x1节点,只是其值被置空了。

        2、三种入队对比:

        3、三种出队对比:

Java并发编程笔记之LinkedBlockingQueue源码探究

       LinkedBlockingQueue 是基于单向链表实现的一种阻塞队列,其内部包含两个节点用于存放队列的现源首尾,并维护了一个表示元素个数的现源原子变量 count。同时,现源它利用了两个 ReentrantLock 实例(takeLock 和 putLock)来保证元素的现源股票配置 源码原子性入队与出队操作。此外,现源notEmpty 和 notFull 两个信号量与条件队列用于实现阻塞操作,现源使得生产者和消费者模型得以实现。现源

       LinkedBlockingQueue 的现源实现主要依赖于其内部锁机制和信号量管理。构造函数默认容量为最大整数值,现源用户可自定义容量大小。现源offer 方法用于尝试将元素添加至队列尾部,现源若队列未满则成功,现源返回 true,现源反之返回 false。若元素为 null,则抛出 NullPointerException。put 方法尝试将元素添加至队列尾部,并阻塞当前线程直至队列有空位,若被中断则抛出 InterruptedException。通过使用 putLock 锁,确保了元素的原子性添加以及元素计数的原子性更新。

       在实现细节上,offer 方法通过在获取 putLock 的同时检查队列是否已满,避免了不必要的元素添加。若队列未满,则执行入队操作并更新计数器,同时考虑唤醒等待队列未满的线程。此过程中,通过 notFull 信号量与条件队列协调线程间等待与唤醒。

       put 方法则在获取 putLock 后立即检查队列是否满,若满则阻塞当前线程至 notFull 信号量被唤醒。在入队后,spring源码网更新计数器,并考虑唤醒等待队列未满的线程,同样通过 notFull 信号量实现。

       poll 方法用于从队列头部获取并移除元素,若队列为空则返回 null。此方法通过获取 takeLock 锁,保证了在检查队列是否为空和执行出队操作之间的原子性。在出队后,计数器递减,并考虑激活因调用 poll 或 take 方法而被阻塞的线程。

       peek 方法类似,但不移除队列头部元素,返回 null 若队列为空。此方法也通过获取 takeLock 锁来保证操作的原子性。

       take 方法用于阻塞获取队列头部元素并移除,若队列为空则阻塞当前线程直至队列不为空。此方法与 put 方法类似,通过 notEmpty 信号量与条件队列协调线程间的等待与唤醒。

       remove 方法用于移除并返回指定元素,若存在则返回 true,否则返回 false。此方法通过双重加锁机制(fullyLock 和 fullyUnlock)来确保元素移除操作的原子性。

       size 方法用于返回当前队列中的元素数量,通过 count.get() 直接获取,确保了操作的准确性。

       综上所述,LinkedBlockingQueue 通过其独特的锁机制和信号量管理,实现了高效、线程安全的阻塞队列操作,适用于生产者-消费者模型等场景。

还不了解Java的5大BlockingQueue阻塞队列源码,看这篇文章就够了

       引言

       本文将详细解读Java中常见的5种BlockingQueue阻塞队列,包括它们的套料实例源码优缺点、区别以及典型应用场景,以帮助深入理解这5种队列的独特性质和使用场合。

       常见的BlockingQueue有以下5种:

       1. **基于数组实现的阻塞队列**:创建时需指定容量大小,是有限队列。

       2. **基于链表实现的阻塞队列**:默认无界,可自定义容量。

       3. **无缓冲阻塞队列**:生产的数据需立即被消费,无缓冲。

       4. **优先级阻塞队列**:支持元素按照大小排序,无界。

       5. **延迟阻塞队列**:基于PriorityQueue实现,无界。

       **BlockingQueue简介

**

       BlockingQueue作为接口,定义了放数据和取数据的多组方法,适用于并发多线程环境,特别适合生产者-消费者模式。

       **应用场景

**

       BlockingQueue的作用类似于消息队列,用于解耦、异步处理和削峰,适用于线程池的核心功能实现。

       **区别与比较

**

       - **ArrayBlockingQueue**:基于数组实现,容量可自定义。

       - **LinkedBlockingQueue**:基于链表实现,无界或自定义容量。

       - **SynchronousQueue**:同步队列,生产者和消费者直接交互,无需缓冲。

       - **PriorityBlockingQueue**:实现优先级排序,无界队列。

       - **DelayQueue**:本地延迟队列,支持元素延迟执行。

       在选择使用哪种队列时,需考虑具体任务的宝监工源码特性、吞吐量需求以及是否需要优先级排序或延迟执行。

       本文旨在提供全面理解Java中BlockingQueue的指南,从源码剖析到应用场景,帮助开发者更好地应用这些工具于实际项目中。

java阻塞队列的操作方法有哪些?

       学习《解读Java源码专栏》,深入Java核心组件源码,内容包含集合、线程、线程池、并发、队列等,了解设计思想和实现细节,应对工作面试。

       解读Java源码系列第9篇,聚焦Java阻塞队列 - BlockingQueue。

       阻塞队列BlockingQueue在并发多线程中广泛应用,尤其是在生产者-消费者模式场景。其作用类似于线程池和消息队列,用于数据的异步处理和削峰。

       BlockingQueue作为接口,定义了放数据和取数据的方法,适用于不同场景。

       常见的BlockingQueue实现包括基于数组和链表的有界队列、无缓冲队列、优先级队列、延迟队列等。

       ArrayBlockingQueue是基于数组实现的有界队列,使用ReentrantLock保证线程安全,并设有条件等待,确保队列的先进先出特性。

       ArrayBlockingQueue的初始化方法提供了不同容量设置。放数据方法包括offer和put,其中offer在队满时返回false,汇编转换+源码put方法阻塞直至空间可用。

       弹出数据方法poll和take在队空时返回null或阻塞,peek和element分别用于查看队首元素。

       ArrayBlockingQueue源码简洁,实现队列核心功能,遵循先进先出原则,适用于需要控制数据进出的并发场景。

       本文解析ArrayBlockingQueue源码,展现其实现细节,了解其作为阻塞队列的关键特性,为深入学习Java并发机制奠定基础。下篇将继续探讨其他阻塞队列实现。

从源码全面解析 LinkedBlockingQueue的来龙去脉

       并发编程是互联网技术的核心,面试官常在此领域对求职者进行深入考察。为了帮助读者在面试中占据优势,本文将解析 LinkedBlockingQueue 的工作原理。

       阻塞队列是并发编程中常见的数据结构,它在生产者和消费者模型中扮演重要角色。生产者负责向队列中添加元素,而消费者则从队列中取出元素。LinkedBlockingQueue 是 Java 中的一种高效阻塞队列实现,它底层基于链表结构。

       在初始化阶段,LinkedBlockingQueue 不需要指定队列大小。除了基本成员变量,它还包含两把锁,分别用于读取和写入操作。有读者疑惑,为何需要两把锁,而其他队列只用一把?本文后续将揭晓答案。

       生产者使用 `add()`、`offer()`、`offer(time)` 和 `put()` 方法向队列中添加元素。消费者则通过 `remove()`、`poll()`、`poll(time)` 和 `take()` 方法从队列中获取元素。

       在解析源码时,发现 LinkedBlockingQueue 与 ArrayBlockingQueue 在锁的使用上有所不同。ArrayBlockingQueue 使用互斥锁,而 LinkedBlockingQueue 使用读锁和写锁。这是否意味着 ArrayBlockingQueue 可以使用相同类型的锁?答案是肯定的,且使用两把锁的 ArrayBlockingQueue 在性能上有所提升。

       流程图展示了 LinkedBlockingQueue 和 ArrayBlockingQueue 之间的相似之处。有兴趣的读者可以自行绘制。

       总结而言,LinkedBlockingQueue 是一种高效的阻塞队列实现,其底层结构基于链表。它通过读锁和写锁管理线程安全,为生产者和消费者提供了并发支持。通过优化锁的使用,LinkedBlockingQueue 在某些场景下展现出更好的性能。

       互联网寒冬虽在,但学习和分享是抵御寒冬的最佳方式。通过交流经验,可以减少弯路,提高效率。如果你对后端架构和中间件源码感兴趣,欢迎与我交流,共同进步。

阿里面试官:你了解过延迟队列DelayQueue的底层实现原理吗?

       欢迎加入《深入探索Java源码系列》学习,这里我们将一起剖析Java核心组件的底层实现,包括集合、线程、并发与队列等领域,为面试做好充分准备。

       这是系列的第部分,我们将一起研究Java中的DelayQueue,它是一个本地延迟队列,常用于处理在指定时间后执行的任务,如5秒后的定时任务。它的工作原理和使用方式值得深入理解。

       DelayQueue的关键在于它如何管理任务的插入和取出,以及如何根据任务的到期时间进行排序。它基于BlockingQueue接口,提供了四组操作方法,如offer、add、put和take等,满足不同场景需求。同时,它内部使用ReentrantLock保证线程安全,Condition负责处理队列中的条件等待。

       DelayQueue的类结构包括一些重要属性,如元素需实现Delayed接口,以及用于同步的ReentrantLock和Condition。初始化可通过无参构造或指定元素集合的方式进行。下面通过示例来演示如何使用和理解其源码。

       首先,创建一个延迟任务,实现Delayed接口,定义getDelay()和compareTo()方法。运行测试后,任务会按到期时间排序执行,take()方法会阻塞直到有任务到期。

       放数据源码中,offer()方法负责插入元素,如果队列已满,会返回false。其他方法如add、put和offer(e, time, unit)都是基于offer方法实现,各有其特定功能。弹出数据的方法,如poll、remove和take,根据队列状态进行操作,如阻塞或抛出异常。

       总结来说,DelayQueue的核心在于其对任务的排序和等待机制。源码简单明了,但理解其工作原理有助于在面试中应对相关问题。在接下来的文章中,我们还将继续探索其他类型的阻塞队列。

喜提JDK的BUG一枚!多线程的情况下请谨慎使用这个类的stream遍历。

       在探讨问题之前,我们先回顾一下 LinkedBlockingQueue 的线程安全性。在传统的观点中,LinkedBlockingQueue 是线程安全的,因为它内部使用了 ReentrantLock。然而,就在 RocketMQ 的讨论版中,一个问题揭示了 LinkedBlockingQueue 在特定情况下的线程不安全性,引发了我们的好奇心。

       核心问题在于 LinkedBlockingQueue 的 stream 遍历方式,在多线程环境下可能出现死循环。我们通过一个简单的 demo 来深入分析这一现象。首先,引入了一个链接,其中详细展示了如何在多线程环境下复现这一 Bug。

       在分析代码之前,让我们先明确 demo 的基本逻辑:创建了 个线程,每个线程不断调用 offer 和 remove 方法。主线程则通过 stream 对 queue 进行遍历,目标是找到队列中的第一个非空元素。这看似是一个简单的遍历操作,但事实并非如此。

       关键点在于 tryAdvance 方法,看似平凡的遍历操作隐藏了陷阱。当运行代码时,预期的输出并未出现,而是陷入了一个死循环,控制台仅输出了一行信息或交替输出几次后停止。

       我们的疑问指向了 JDK 版本,尤其是 JDK 8。通过替换为 JDK ,我们观察到交替输出的效果。这使得我们大胆推测,这可能是 JDK 8 版本的 Bug。为了验证这一假设,我们进行了详细的分析。

       通过线程 dump 文件,我们发现主线程始终处于可运行状态,似乎没有被锁阻塞。然而,从控制台的输出来看,它似乎处于阻塞状态。这一现象让我们联想到一个经典的场景:线程陷入死循环。

       通过深入源码分析,我们发现了死循环的根源。在 stream 遍历的关键方法 tryAdvance 中,存在一个 while 循环,其条件始终满足,导致死循环。而问题的核心在于移除队列头部元素的代码逻辑,当有其他线程不断调用 remove 方法时,可能会形成特定的节点结构,触发死循环。

       经过详细的分析,我们揭示了这一 Bug 的原理,并通过简化代码演示了整个过程。通过将实例代码简化,我们揭示了死循环是如何在多线程环境下产生的。这不仅有助于理解 Bug 的本质,也为后续的 Bug 修复提供了思路。

       为了验证解决方案的正确性,我们对比了 JDK 8 和 JDK 的源码差异。在 JDK 中,通过引入了一个名为 succ 的方法,成功解决了死循环问题。这一方法通过确保节点不会指向自身,从而避免了死循环的产生。

       通过这篇文章的分析,我们不仅揭示了 LinkedBlockingQueue 在特定条件下的线程不安全性,还探讨了如何通过升级 JDK 版本、避免使用 stream 遍历,以及使用 synchronized 修饰符等方式来规避此类问题。同时,我们还延伸至其他数据结构,如 ConcurrentHashMap,讨论了它们在不同使用场景下的线程安全性问题。

       最后,我们再次强调在多线程环境下,LinkedBlockingQueue 的 stream 遍历方式可能存在一定的问题,可能会导致死循环。理解并解决这类 Bug,对于确保代码的健壮性和性能至关重要。