• Java线程池的任务消息队列


    多线程队列

    Java多线程包括线程池会用到缓存任务的队列,Java提供的线程安全队列分为两种:阻塞队列非阻塞队列

    1.阻塞队列

    阻塞队列支持生产者模式和消费者模式互相等待,队列为空,消费线程阻塞,直到队列不为空;当队列满时,生产线程会阻塞,直到队列不满。

    Java ThreadPool中也用到阻塞队列,当创建的线程数超过核心线程数,新提交的任务会被push到阻塞队列中。根据自己的业务可以选择不同的队列。

    阻塞队列
    队列类型说明
    ArrayBlockingQueue
    • 基于数组
    • FIFO(先进先出)原则对元素排序
    • ReentrantLock、Condition 实现线程安全
    LinkedBlockingQueue
    • 基于链表
    • FIFO原则对元素排序
    • ReentrantLock、Condition 实现线程安全
    • 吞吐量高于ArrayBlockingQueue
    PriorityBlockingQueue
    • 基于二叉树实现的无界限(最大值Integer.MAX_VALUE - 8)阻塞队列
    • 有优先级的无限阻塞队列
    • 无排序
    • 数据变更时,将最小或最大数据放在最上面节点上
    • ReentrantLock、Condition 实现的线程安全
    DelayQueue
    • 支持延时获取元素的无界阻塞队列
    • 基于 PriorityBlockingQueue 扩展实现
    • 实现了Delay 延时接口
    SynchronousQueue
    • 不存储多个元素的阻塞队列
    • 放入元素时要等相应的消费者取走元素才可放入
    • 使用两种模式管理数据:1.后进先出 2.先进先出

    Java线程池Executors实现的四种类型的ThreadPoolExecutor对应上面的缓存队列详情如下:

    线程池实现队列
    newCachedThreadPoolSynchronousQueue
    newFixedThreadPoolLinkedBlockingQueue
    newScheduledThreadPoolDelayQueue
    newSingleThreadExecutorLinkedBlockingQueue

     2.非阻塞队列

    常用的非阻塞线程安全队列是ConcurrentLinkedQueue,一种无界限队列。FIFO原则,基于链表实现,CAS乐观锁保证线程安全。

    构造函数:

    • head、tail节点组成
    • 每个节点(Node)由节点元素(item)和指向下一个节点的引用 (next) 组成
    • 节点与节点之间通过 next 关联
    • 队列初始化时, head 节点存储的元素为空,tail 节点等于 head 节点

    1. public ConcurrentLinkedQueue() {
    2. head = tail = new Node(null);
    3. }
    4. private static class Node {
    5. volatile E item;
    6. volatile Node next;
    7. .
    8. .
    9. }

    入列:一个线程入列一个数据时,会将该数据封装成一个 Node 节点,并先获取到队列的队尾节点,当确定此时队尾节点的 next 值为 null 之后,再通过 CAS 将新队尾节点的 next 值设为新节点。此时 p != t,也就是设置 next 值成功,然后再通过 CAS 将队尾节点设置为当前节点即可。

    1. public boolean offer(E e) {
    2. checkNotNull(e);
    3. //创建入队节点
    4. final Node newNode = new Node(e);
    5. //t,p为尾节点,默认相等,采用失败即重试的方式,直到入队成功
    6. for (Node t = tail, p = t;;) {
    7. //获取队尾节点的下一个节点
    8. Node q = p.next;
    9. //如果q为null,则代表p就是队尾节点
    10. if (q == null) {
    11. //将入列节点设置为当前队尾节点的next节点
    12. if (p.casNext(null, newNode)) {
    13. //判断tail节点和p节点距离达到两个节点
    14. if (p != t) // hop two nodes at a time
    15. //如果tail不是尾节点则将入队节点设置为tail。
    16. // 如果失败了,那么说明有其他线程已经把tail移动过
    17. casTail(t, newNode); // Failure is OK.
    18. return true;
    19. }
    20. }
    21. // 如果p节点等于p的next节点,则说明p节点和q节点都为空,表示队列刚初始化,所以返回
    22. else if (p == q)
    23. p = (t != (t = tail)) ? t : head;
    24. else
    25. // Check for tail updates after two hops.
    26. p = (p != t && t != (t = tail)) ? t : q;
    27. }
    28. }

    出列: 首先获取 head 节点,并判断 item 是否为 null,如果为空,则表示已经有一个线程刚刚进行了出列操作,然后更新 head 节点;如果不为空,则使用 CAS 操作将 head 节点设置为 null,CAS 就会成功地直接返回节点元素,否则还是更新 head 节点

    1. public E poll() {
    2. // 设置起始点
    3. restartFromHead:
    4. for (;;) {
    5. //p获取head节点
    6. for (Node h = head, p = h, q;;) {
    7. //获取头节点元素
    8. E item = p.item;
    9. //如果头节点元素不为null,通过cas设置p节点引用的元素为null
    10. if (item != null && p.casItem(item, null)) {
    11. // Successful CAS is the linearization point
    12. // for item to be removed from this queue.
    13. if (p != h) // hop two nodes at a time
    14. updateHead(h, ((q = p.next) != null) ? q : p);
    15. return item;
    16. }
    17. //如果p节点的下一个节点为null,则说明这个队列为空,更新head结点
    18. else if ((q = p.next) == null) {
    19. updateHead(h, p);
    20. return null;
    21. }
    22. //节点出队失败,重新跳到restartFromHead来进行出队
    23. else if (p == q)
    24. continue restartFromHead;
    25. else
    26. p = q;
    27. }
    28. }
    29. }

    结论:ConcurrentLinkedQueue 是基于 CAS 乐观锁实现的,在并发时的性能要好于其它阻塞队列,因此很适合作为高并发场景下的排队队列。

    Disruptor

    Disruptor是英国外汇交易公司LMAX开发的一个高性能队列,研发的初衷是解决内存队列的延迟问题。

    为什么性能高?

    • CAS
    • 消除伪共享
    • RingBuffer

    最后

    我们看到Executors提供的很多默认方法使用的都是无界的LinkedBlockingQueue,高负载的情况下无界队列很容易导致OOM,OOM会导致stop world,这是致命的,所以不建议使用Executors。建议使用使用了有界队列的线程池。

    当没有设置拒绝策略时会抛出RejectedExecutionException运行时异常,并不会强制抛出,所以任务比较重要时,务必要自己实现拒绝策略。自定义拒绝策略往往和降级策略(重要的任务可以存入数据库或消息队列,启用另外的补偿线程池去做消费)搭配使用。

  • 相关阅读:
    微信小程序本地生活(列表页面)
    工程项目安全管理系统安质保智慧建造云平台3.0帮建设单位管好工程项目
    排序算法之-快速
    ES 集群统计 API返回结果解析说明
    应用案例|基于三维机器视觉的机器人引导电动汽车充电头自动插拔应用方案
    python笔记7(函数补充)
    #力扣:125. 验证回文串@FDDLC
    Graph RAG: 知识图谱结合 LLM 的检索增强
    Nvm 安装
    推出全新分布式计算接口,OneFlow v0.7.0发布,LiBai代码库、Serving、MLIR一应俱全...
  • 原文地址:https://blog.csdn.net/merryxuan/article/details/126010583