百度360必应搜狗淘宝本站头条
当前位置:网站首页 > 技术分析 > 正文

死磕 java集合之ArrayBlockingQueue源码分析

liebian365 2024-11-12 13:11 6 浏览 0 评论

(手机横屏看源码更方便)

问题

(1)ArrayBlockingQueue的实现方式?

(2)ArrayBlockingQueue是否需要扩容?

(3)ArrayBlockingQueue有什么缺点?

简介

ArrayBlockingQueue是java并发包下一个以数组实现的阻塞队列,它是线程安全的,至于是否需要扩容,请看下面的分析。

队列

队列,是一种线性表,它的特点是先进先出,又叫FIFO,就像我们平常排队一样,先到先得,即先进入队列的人先出队。

源码分析

主要属性

// 使用数组存储元素
final Object[] items;
// 取元素的指针
int takeIndex;
// 放元素的指针
int putIndex;
// 元素数量
int count;
// 保证并发访问的锁
final ReentrantLock lock;
// 非空条件
private final Condition notEmpty;
// 非满条件
private final Condition notFull;

通过属性我们可以得出以下几个重要信息:

(1)利用数组存储元素;

(2)通过放指针和取指针来标记下一次操作的位置;

(3)利用重入锁来保证并发安全;

主要构造方法

public ArrayBlockingQueue(int capacity) {
 this(capacity, false);
}
public ArrayBlockingQueue(int capacity, boolean fair) {
 if (capacity <= 0)
 throw new IllegalArgumentException();
 // 初始化数组
 this.items = new Object[capacity];
 // 创建重入锁及两个条件
 lock = new ReentrantLock(fair);
 notEmpty = lock.newCondition();
 notFull = lock.newCondition();
}

通过构造方法我们可以得出以下两个结论:

(1)ArrayBlockingQueue初始化时必须传入容量,也就是数组的大小;

(2)可以通过构造方法控制重入锁的类型是公平锁还是非公平锁;

入队

入队有四个方法,它们分别是add(E e)、offer(E e)、put(E e)、offer(E e, long timeout, TimeUnit unit),它们有什么区别呢?

public boolean add(E e) {
 // 调用父类的add(e)方法
 return super.add(e);
}
// super.add(e)【本篇文章由公众号“彤哥读源码”原创】
public boolean add(E e) {
 // 调用offer(e)如果成功返回true,如果失败抛出异常
 if (offer(e))
 return true;
 else
 throw new IllegalStateException("Queue full");
}
public boolean offer(E e) {
 // 元素不可为空
 checkNotNull(e);
 final ReentrantLock lock = this.lock;
 // 加锁
 lock.lock();
 try {
 if (count == items.length)
 // 如果数组满了就返回false
 return false;
 else {
 // 如果数组没满就调用入队方法并返回true
 enqueue(e);
 return true;
 }
 } finally {
 // 解锁
 lock.unlock();
 }
}
public void put(E e) throws InterruptedException {
 checkNotNull(e);
 final ReentrantLock lock = this.lock;
 // 加锁,如果线程中断了抛出异常
 lock.lockInterruptibly();
 try {
 // 如果数组满了,使用notFull等待
 // notFull等待的意思是说现在队列满了
 // 只有取走一个元素后,队列才不满
 // 然后唤醒notFull,然后继续现在的逻辑
 // 这里之所以使用while而不是if
 // 是因为有可能多个线程阻塞在lock上
 // 即使唤醒了可能其它线程先一步修改了队列又变成满的了
 // 这时候需要再次等待
 while (count == items.length)
 notFull.await();
 // 入队
 enqueue(e);
 } finally {
 // 解锁
 lock.unlock();
 }
}
public boolean offer(E e, long timeout, TimeUnit unit)
 throws InterruptedException {
 checkNotNull(e);
 long nanos = unit.toNanos(timeout);
 final ReentrantLock lock = this.lock;
 // 加锁
 lock.lockInterruptibly();
 try {
 // 如果数组满了,就阻塞nanos纳秒
 // 如果唤醒这个线程时依然没有空间且时间到了就返回false
 while (count == items.length) {
 if (nanos <= 0)
 return false;
 nanos = notFull.awaitNanos(nanos);
 }
 // 入队
 enqueue(e);
 return true;
 } finally {
 // 解锁
 lock.unlock();
 }
}
private void enqueue(E x) {
 final Object[] items = this.items;
 // 把元素直接放在放指针的位置上
 items[putIndex] = x;
 // 如果放指针到数组尽头了,就返回头部
 if (++putIndex == items.length)
 putIndex = 0;
 // 数量加1
 count++;
 // 唤醒notEmpty,因为入队了一个元素,所以肯定不为空了
 notEmpty.signal();
}

(1)add(e)时如果队列满了则抛出异常;

(2)offer(e)时如果队列满了则返回false;

(3)put(e)时如果队列满了则使用notFull等待;

(4)offer(e, timeout, unit)时如果队列满了则等待一段时间后如果队列依然满就返回false;

(5)利用放指针循环使用数组来存储元素;

出队

出队有四个方法,它们分别是remove()、poll()、take()、poll(long timeout, TimeUnit unit),它们有什么区别呢?

public E remove() {
 // 调用poll()方法出队
 E x = poll();
 if (x != null)
 // 如果有元素出队就返回这个元素
 return x;
 else
 // 如果没有元素出队就抛出异常
 throw new NoSuchElementException();
}
public E poll() {
 final ReentrantLock lock = this.lock;
 // 加锁
 lock.lock();
 try {
 // 如果队列没有元素则返回null,否则出队
 return (count == 0) ? null : dequeue();
 } finally {
 lock.unlock();
 }
}
public E take() throws InterruptedException {
 final ReentrantLock lock = this.lock;
 // 加锁
 lock.lockInterruptibly();
 try {
 // 如果队列无元素,则阻塞等待在条件notEmpty上
 while (count == 0)
 notEmpty.await();
 // 有元素了再出队
 return dequeue();
 } finally {
 // 解锁【本篇文章由公众号“彤哥读源码”原创】
 lock.unlock();
 }
}
public E poll(long timeout, TimeUnit unit) throws InterruptedException {
 long nanos = unit.toNanos(timeout);
 final ReentrantLock lock = this.lock;
 // 加锁
 lock.lockInterruptibly();
 try {
 // 如果队列无元素,则阻塞等待nanos纳秒
 // 如果下一次这个线程获得了锁但队列依然无元素且已超时就返回null
 while (count == 0) {
 if (nanos <= 0)
 return null;
 nanos = notEmpty.awaitNanos(nanos);
 }
 return dequeue();
 } finally {
 lock.unlock();
 }
}
private E dequeue() {
 final Object[] items = this.items;
 @SuppressWarnings("unchecked")
 // 取取指针位置的元素
 E x = (E) items[takeIndex];
 // 把取指针位置设为null
 items[takeIndex] = null;
 // 取指针前移,如果数组到头了就返回数组前端循环利用
 if (++takeIndex == items.length)
 takeIndex = 0;
 // 元素数量减1
 count--;
 if (itrs != null)
 itrs.elementDequeued();
 // 唤醒notFull条件
 notFull.signal();
 return x;
}

(1)remove()时如果队列为空则抛出异常;

(2)poll()时如果队列为空则返回null;

(3)take()时如果队列为空则阻塞等待在条件notEmpty上;

(4)poll(timeout, unit)时如果队列为空则阻塞等待一段时间后如果还为空就返回null;

(5)利用取指针循环从数组中取元素;

总结

(1)ArrayBlockingQueue不需要扩容,因为是初始化时指定容量,并循环利用数组;

(2)ArrayBlockingQueue利用takeIndex和putIndex循环利用数组;

(3)入队和出队各定义了四组方法为满足不同的用途;

(4)利用重入锁和两个条件保证并发安全;

彩蛋

(1)论BlockingQueue中的那些方法?

BlockingQueue是所有阻塞队列的顶级接口,它里面定义了一批方法,它们有什么区别呢?

操作 抛出异常 返回特定值 阻塞 超时 入队 add(e) offer(e)——false put(e) offer(e, timeout, unit) 出队 remove() poll()——null take() poll(timeout, unit) 检查 element() peek()——null - - (2)ArrayBlockingQueue有哪些缺点呢?

a)队列长度固定且必须在初始化时指定,所以使用之前一定要慎重考虑好容量;

b)如果消费速度跟不上入队速度,则会导致提供者线程一直阻塞,且越阻塞越多,非常危险;

c)只使用了一个锁来控制入队出队,效率较低,那是不是可以借助分段的思想把入队出队分裂成两个锁呢?且听下回分解。

相关推荐

快递查询教程,批量查询物流,一键管理快递

作为商家,每天需要查询许许多多的快递单号,面对不同的快递公司,有没有简单一点的物流查询方法呢?小编的回答当然是有的,下面随小编一起来试试这个新技巧。需要哪些工具?安装一个快递批量查询高手快递单号怎么快...

一键自动查询所有快递的物流信息 支持圆通、韵达等多家快递

对于各位商家来说拥有一个好的快递软件,能够有效的提高自己的工作效率,在管理快递单号的时候都需要对单号进行表格整理,那怎么样能够快速的查询所有单号信息,并自动生成表格呢?1、其实方法很简单,我们不需要一...

快递查询单号查询,怎么查物流到哪了

输入单号怎么查快递到哪里去了呢?今天小编给大家分享一个新的技巧,它支持多家快递,一次能查询多个单号物流,还可对查询到的物流进行分析、筛选以及导出,下面一起来试试。需要哪些工具?安装一个快递批量查询高手...

3分钟查询物流,教你一键批量查询全部物流信息

很多朋友在问,如何在短时间内把单号的物流信息查询出来,查询完成后筛选已签收件、筛选未签收件,今天小编就分享一款物流查询神器,感兴趣的朋友接着往下看。第一步,运行【快递批量查询高手】在主界面中点击【添...

快递单号查询,一次性查询全部物流信息

现在各种快递的查询方式,各有各的好,各有各的劣,总的来说,还是有比较方便的。今天小编就给大家分享一个新的技巧,支持多家快递,一次能查询多个单号的物流,还能对查询到的物流进行分析、筛选以及导出,下面一起...

快递查询工具,批量查询多个快递快递单号的物流状态、签收时间

最近有朋友在问,怎么快速查询单号的物流信息呢?除了官网,还有没有更简单的方法呢?小编的回答当然是有的,下面一起来看看。需要哪些工具?安装一个快递批量查询高手多个京东的快递单号怎么快速查询?进入快递批量...

快递查询软件,自动识别查询快递单号查询方法

当你拥有多个快递单号的时候,该如何快速查询物流信息?比如单号没有快递公司时,又该如何自动识别再去查询呢?不知道如何操作的宝贝们,下面随小编一起来试试。需要哪些工具?安装一个快递批量查询高手快递单号若干...

教你怎样查询快递查询单号并保存物流信息

商家发货,快递揽收后,一般会直接手动复制到官网上一个个查询物流,那么久而久之,就会觉得查询变得特别繁琐,今天小编给大家分享一个新的技巧,下面一起来试试。教程之前,我们来预览一下用快递批量查询高手...

简单几步骤查询所有快递物流信息

在高峰期订单量大的时候,可能需要一双手当十双手去查询快递物流,但是由于逐一去查询,效率极低,追踪困难。那么今天小编给大家分享一个新的技巧,一次能查询多个快递单号的物流,下面一起来学习一下,希望能给大家...

物流单号查询,如何查询快递信息,按最后更新时间搜索需要的单号

最近有很多朋友在问,如何通过快递单号查询物流信息,并按最后更新时间搜索出需要的单号呢?下面随小编一起来试试吧。需要哪些工具?安装一个快递批量查询高手快递单号若干怎么快速查询?运行【快递批量查询高手】...

连续保存新单号功能解析,导入单号查询并自动识别批量查快递信息

快递查询已经成为我们日常生活中不可或缺的一部分。然而,面对海量的快递单号,如何高效、准确地查询每一个快递的物流信息,成为了许多人头疼的问题。幸运的是,随着科技的进步,一款名为“快递批量查询高手”的软件...

快递查询教程,快递单号查询,筛选更新量为1的单号

最近有很多朋友在问,怎么快速查询快递单号的物流,并筛选出更新量为1的单号呢?今天小编给大家分享一个新方法,一起来试试吧。需要哪些工具?安装一个快递批量查询高手多个快递单号怎么快速查询?运行【快递批量查...

掌握批量查询快递动态的技巧,一键查找无信息记录的两种方法解析

在快节奏的商业环境中,高效的物流查询是确保业务顺畅运行的关键。作为快递查询达人,我深知时间的宝贵,因此,今天我将向大家介绍一款强大的工具——快递批量查询高手软件。这款软件能够帮助你批量查询快递动态,一...

从复杂到简单的单号查询,一键清除单号中的符号并批量查快递信息

在繁忙的商务与日常生活中,快递查询已成为不可或缺的一环。然而,面对海量的单号,逐一查询不仅耗时费力,还容易出错。现在,有了快递批量查询高手软件,一切变得简单明了。只需一键,即可搞定单号查询,一键处理单...

物流单号查询,在哪里查询快递

如果在快递单号多的情况,你还在一个个复制粘贴到官网上手动查询,是一件非常麻烦的事情。于是乎今天小编给大家分享一个新的技巧,下面一起来试试。需要哪些工具?安装一个快递批量查询高手快递单号怎么快速查询?...

取消回复欢迎 发表评论: