jdk11源码--LinkedBlockingQueue源码分析

简介: jdk11 LinkedBlockingQueue源码分析

概述

上一篇介绍了jdk11源码--ArrayBlockingQueue源码分析,接下来看一下LinkedBlockingQueue的实现。
这两个阻塞队列最大的区别就是底层元素存储实现不同,ArrayBlockingQueue是基于==数组==,而LinkedBlockingQueue是基于==单向链表==。

LinkedBlockingQueue类图如下:
在这里插入图片描述

LinkedBlockingQueue也是==FIFO==先进先出队列,其实现是 ==双锁队列two lock queue== 算法的变体,它内部维护了一个takeLock和一个putLock,也可以理解为读写锁的一种实现方式。
==思想:锁分离,提高性能。==
下面结合源码具体分析。

构造方法

网上有的说LinkedBlockingQueue是无界队列,其实不太准确,具体看下面的源码。
下面两个构造方法,一个是有参数的,设置容量大小。这是有界队列。
一个是无参数的构造方法,其内部实现是设置容量大小为Integer.MAX_VALUE的队列,这就是网络上说的无界队列,其实还是有界的,只不过比较大,是Integer.MAX_VALUE。

public LinkedBlockingQueue() {
    this(Integer.MAX_VALUE);
}
public LinkedBlockingQueue(int capacity) {
    if (capacity <= 0) throw new IllegalArgumentException();
    this.capacity = capacity;
    last = head = new Node<E>(null);//初始化一个头结点
}

链表节点数据结构

上面说了,LinkedBlockingQueue是单向链表,那么我们看一下他的数据结构,只有一个next指针,典型的单向链表结构:

static class Node<E> {
    E item;
    Node<E> next;
    Node(E x) { item = x; }
}

关键属性

/** 队列容量 */
private final int capacity;
/** 当前队列的元素数量 */
private final AtomicInteger count = new AtomicInteger();
/**
 * 链表的头结点
 * 注意:head.item 永远是 null
 */
transient Node<E> head;
/**
 * 链表的尾结点
 * 注意: last.next 永远是 null
 */
private transient Node<E> last;

/** 读锁: take, poll方法使用 */
private final ReentrantLock takeLock = new ReentrantLock();
/** 读锁的condition */
private final Condition notEmpty = takeLock.newCondition();

/** 写锁: put, offer 方法使用 */
private final ReentrantLock putLock = new ReentrantLock();
/** 写锁的condition */
private final Condition notFull = putLock.newCondition();

count :由于LinkedBlockingQueue使用了两个锁,为了支持线程安全,所以使用原子性的AtomicInteger 来统计队列元素的个数。注意这里count的计数不包含head节点。

put

添加元素至队尾

public void put(E e) throws InterruptedException {
    if (e == null) throw new NullPointerException();
    final int c;
    final Node<E> node = new Node<E>(e);
    final ReentrantLock putLock = this.putLock;
    final AtomicInteger count = this.count;
    putLock.lockInterruptibly();//加写锁,支持中断
    try {
        //如果队列满了,则当前线程阻塞:添加到condition队列中。
        //这里count是线程安全的
        while (count.get() == capacity) {
            notFull.await();
        }
        enqueue(node);//队列不满,写入队尾
        c = count.getAndIncrement();
        if (c + 1 < capacity)//元素入队后,还有剩余空间,就唤醒notFull这个condition队列的第一个线程
            notFull.signal();
    } finally {
        putLock.unlock();
    }
    if (c == 0)//注意这里c是添加元素前的队列长度(getAndIncrement方法先返回值,再将其加1),
        signalNotEmpty();//原队列长度为0,空的,当前put方法添加了一个元素入队,立马唤醒等待在notEmpty condition的 线程来取数据
}

private void signalNotEmpty() {
    final ReentrantLock takeLock = this.takeLock;
    takeLock.lock();
    try {
        notEmpty.signal();//notEmpty是读锁takeLock 的condition队列
    } finally {
        takeLock.unlock();
    }
}

//添加元素至队尾
//enqueue是在写锁内部的,是线程安全的
private void enqueue(Node<E> node) {
    last = last.next = node;
}

这里来看一下if (c + 1 < capacity) notFull.signal();这行代码,为什么这么写。
原因肯定离不开多线程以及condition的原理。
c + 1 < capacity是来判断是否还有剩余空间,当有剩余空间的时候,就唤醒notFull这个condition队列中等待的第一个节点。首先因为是多线程的,所以可能会有多个线程阻塞在notfull上。可能有人会问,前面不是加了putLock的锁吗,这里只能有一个线程进入啊?提出这个疑问的同学请看博主文章jdk11源码--ReentrantLock之Condition源码分析。这里简单说一下:一个线程执行了notFull.await()阻塞后,该线程添加到condition队列中,释放锁,其他线程就可以继续获取锁执行了
当然,这里每次put,空间不满时,都去唤醒一下,确实有肯能会有部分(甚至是大部分)signal操作,这是使用双锁策略及condition的代价,不过这是值得的。

last = last.next = node;再讲一下这句。其实分解一下等价于:

last.next = node;
last = node;

take

从对头取元素

public E take() throws InterruptedException {
    final E x;
    final int c;
    final AtomicInteger count = this.count;
    final ReentrantLock takeLock = this.takeLock;
    takeLock.lockInterruptibly();//加锁,支持中断
    try {
        while (count.get() == 0) {//如果队列为空,则当前线程阻塞在notEmpty condition上
            notEmpty.await();
        }
        x = dequeue();//取走队首元素
        c = count.getAndDecrement();//获取旧的count数量,然后对其减1
        if (c > 1)
            notEmpty.signal();//如果队列中还有值,那么唤醒阻塞在notEmpty condition的线程。
    } finally {
        takeLock.unlock();
    }
    if (c == capacity)//之前队列满的
        signalNotFull();//之前队列满的,那么这里取走了一个,队列有空位子了,马上唤醒notFull condition的一个线程,让其竞争锁put数据
    return x;
}


//返回head节点后面的第一个节点的数据
//并且将head后移一位,老head准备gc回收
private E dequeue() {
    Node<E> h = head;
    Node<E> first = h.next;
    h.next = h; // 辅助GC (为什么不在下面将head设置为null?感觉这里next引用自己,对外也没有引用了,是可以被回收的,效果差不多)
    head = first;
    E x = first.item;
    first.item = null;
    return x;
}
private void signalNotFull() {
    final ReentrantLock putLock = this.putLock;
    putLock.lock();
    try {
        notFull.signal();
    } finally {
        putLock.unlock();
    }
}

总结

两把锁,各配一个condition:

  • takeLock 和 notEmpty 搭配:先获取 takeLock 锁,才能到链表中获取链表第一个节点的数据。如果此时队列为空,则当前线程加入notEmpty 这个condition队列阻塞。
  • putLock 需要和 notFull 搭配:先获取 putLock 锁,才能到链表中插入(put)一个元素到链尾。如果此时队列满,则当前线程加入notFull 这个condition队列阻塞。

唤醒notFull这个condition队列上的线程有两种情况:

  • put、offer每次入队后,还有剩余空间
  • take、poll前队列是满的,那么take走一个后,就唤醒

唤醒notEmpty这个condition队列上的线程有两种情况:

  • take、poll每次取走元素后,队列中不为空
  • put、offer前队列是空的,那么put进来一个元素后,就唤醒

在这里插入图片描述

相关文章
|
11月前
|
前端开发 Java 关系型数据库
基于Java+Springboot+Vue开发的鲜花商城管理系统源码+运行
基于Java+Springboot+Vue开发的鲜花商城管理系统(前后端分离),这是一项为大学生课程设计作业而开发的项目。该系统旨在帮助大学生学习并掌握Java编程技能,同时锻炼他们的项目设计与开发能力。通过学习基于Java的鲜花商城管理系统项目,大学生可以在实践中学习和提升自己的能力,为以后的职业发展打下坚实基础。技术学习共同进步
670 7
|
11月前
|
消息中间件 算法 安全
JUC并发—1.Java集合包底层源码剖析
本文主要对JDK中的集合包源码进行了剖析。
|
11月前
|
人工智能 安全 Java
智慧工地源码,Java语言开发,微服务架构,支持分布式和集群部署,多端覆盖
智慧工地是“互联网+建筑工地”的创新模式,基于物联网、移动互联网、BIM、大数据、人工智能等技术,实现对施工现场人员、设备、材料、安全等环节的智能化管理。其解决方案涵盖数据大屏、移动APP和PC管理端,采用高性能Java微服务架构,支持分布式与集群部署,结合Redis、消息队列等技术确保系统稳定高效。通过大数据驱动决策、物联网实时监测预警及AI智能视频监控,消除数据孤岛,提升项目可控性与安全性。智慧工地提供专家级远程管理服务,助力施工质量和安全管理升级,同时依托可扩展平台、多端应用和丰富设备接口,满足多样化需求,推动建筑行业数字化转型。
383 5
|
6月前
|
存储 小程序 Java
热门小程序源码合集:微信抖音小程序源码支持PHP/Java/uni-app完整项目实践指南
小程序已成为企业获客与开发者创业的重要载体。本文详解PHP、Java、uni-app三大技术栈在电商、工具、服务类小程序中的源码应用,提供从开发到部署的全流程指南,并分享选型避坑与商业化落地策略,助力开发者高效构建稳定可扩展项目。
|
10月前
|
JavaScript Java 关系型数据库
家政系统源码,java版本
这是一款基于SpringBoot后端框架、MySQL数据库及Uniapp移动端开发的家政预约上门服务系统。
316 6
家政系统源码,java版本
|
10月前
|
供应链 JavaScript 前端开发
Java基于SaaS模式多租户ERP系统源码
ERP,全称 Enterprise Resource Planning 即企业资源计划。是一种集成化的管理软件系统,它通过信息技术手段,将企业的各个业务流程和资源管理进行整合,以提高企业的运营效率和管理水平,它是一种先进的企业管理理念和信息化管理系统。 适用于小微企业的 SaaS模式多租户ERP管理系统, 采用最新的技术栈开发, 让企业简单上云。专注于小微企业的应用需求,如企业基本的进销存、询价,报价, 采购、销售、MRP生产制造、品质管理、仓库库存管理、财务应收付款, OA办公单据、CRM等。
639 23
|
9月前
|
存储 安全 Java
Java 集合面试题从数据结构到 HashMap 源码剖析详解及长尾考点梳理
本文深入解析Java集合框架,涵盖基础概念、常见集合类型及HashMap的底层数据结构与源码实现。从Collection、Map到Iterator接口,逐一剖析其特性与应用场景。重点解读HashMap在JDK1.7与1.8中的数据结构演变,包括数组+链表+红黑树优化,以及put方法和扩容机制的实现细节。结合订单管理与用户权限管理等实际案例,展示集合框架的应用价值,助你全面掌握相关知识,轻松应对面试与开发需求。
434 3
|
11月前
|
Java
【源码】【Java并发】【ConcurrentHashMap】适合中学体质的ConcurrentHashMap
本文深入解析了ConcurrentHashMap的实现原理,涵盖JDK 7与JDK 8的区别、静态代码块、构造方法、put/get/remove核心方法等。JDK 8通过Node数组+链表/红黑树结构优化并发性能,采用CAS和synchronized实现高效锁机制。文章还详细讲解了hash计算、表初始化、扩容协助及计数更新等关键环节,帮助读者全面掌握ConcurrentHashMap的工作机制。
274 6
【源码】【Java并发】【ConcurrentHashMap】适合中学体质的ConcurrentHashMap
|
11月前
|
Java
【源码】【Java并发】【LinkedBlockingQueue】适合中学体质的LinkedBlockingQueue入门
前言 有了前文对简单实用的学习 【Java并发】【LinkedBlockingQueue】适合初学体质的LinkedBlockingQueue入门 聪明的你,一定会想知道更多。哈哈哈哈哈,下面主播就...
235 6
【源码】【Java并发】【LinkedBlockingQueue】适合中学体质的LinkedBlockingQueue入门
|
11月前
|
Java 关系型数据库 MySQL
Java汽车租赁系统源码(含数据库脚本)
Java汽车租赁系统源码(含数据库脚本)
398 4