问题

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


欢迎关注我的公众号“彤哥读源码”,查看更多源码系列文章, 与彤哥一起畅游源码的海洋。

死磕 java集合之ArrayBlockingQueue源码分析的更多相关文章

  1. 死磕 java集合之DelayQueue源码分析

    问题 (1)DelayQueue是阻塞队列吗? (2)DelayQueue的实现方式? (3)DelayQueue主要用于什么场景? 简介 DelayQueue是java并发包下的延时阻塞队列,常用于 ...

  2. 死磕 java集合之PriorityBlockingQueue源码分析

    问题 (1)PriorityBlockingQueue的实现方式? (2)PriorityBlockingQueue是否需要扩容? (3)PriorityBlockingQueue是怎么控制并发安全的 ...

  3. 死磕 java集合之PriorityQueue源码分析

    问题 (1)什么是优先级队列? (2)怎么实现一个优先级队列? (3)PriorityQueue是线程安全的吗? (4)PriorityQueue就有序的吗? 简介 优先级队列,是0个或多个元素的集合 ...

  4. 死磕 java集合之CopyOnWriteArraySet源码分析——内含巧妙设计

    问题 (1)CopyOnWriteArraySet是用Map实现的吗? (2)CopyOnWriteArraySet是有序的吗? (3)CopyOnWriteArraySet是并发安全的吗? (4)C ...

  5. 死磕 java集合之LinkedHashSet源码分析

    问题 (1)LinkedHashSet的底层使用什么存储元素? (2)LinkedHashSet与HashSet有什么不同? (3)LinkedHashSet是有序的吗? (4)LinkedHashS ...

  6. 死磕 java集合之ConcurrentHashMap源码分析(三)

    本章接着上两章,链接直达: 死磕 java集合之ConcurrentHashMap源码分析(一) 死磕 java集合之ConcurrentHashMap源码分析(二) 删除元素 删除元素跟添加元素一样 ...

  7. 死磕 java集合之ArrayDeque源码分析

    问题 (1)什么是双端队列? (2)ArrayDeque是怎么实现双端队列的? (3)ArrayDeque是线程安全的吗? (4)ArrayDeque是有界的吗? 简介 双端队列是一种特殊的队列,它的 ...

  8. 【死磕 Java 集合】— ConcurrentSkipListMap源码分析

    转自:http://cmsblogs.com/?p=4773 [隐藏目录] 前情提要 简介 存储结构 源码分析 主要内部类 构造方法 添加元素 添加元素举例 删除元素 删除元素举例 查找元素 查找元素 ...

  9. 死磕 java集合之LinkedList源码分析

    问题 (1)LinkedList只是一个List吗? (2)LinkedList还有其它什么特性吗? (3)LinkedList为啥经常拿出来跟ArrayList比较? (4)我为什么把LinkedL ...

随机推荐

  1. es6(一):es6介绍以及let,const

    es是js的规范,而js是具体实现 将es6转化为es5代码工具:运用的多的是babel 在线转换地址:babel,traceur(属于谷歌)   1.let申明变量:let其实可以完全取代var,并 ...

  2. java线程之线程通信控制

    在上篇我们看到,A线程往公共资源库(对象)提供了一条数据,然后B线程从库中提取了数据并打印出来. 实际项目中,我们不可能只往库中提供一条数据,而且库的大小也不会是无穷大的,那么我们就会有这样一 个需求 ...

  3. 基于NetMQ的TLS框架NetMQ.Security的实现分析

    基于NetMQ的TLS框架NetMQ.Security的实现分析 前言 介绍 交互过程 支持的协议 TLS协议 支持的算法 实现 握手 第一次握手 Client Hello 第二次握手 Server ...

  4. 用js来实现那些数据结构—目录

    首先,有一点要声明,下面所有文章的所有内容的代码,都不是我一个人独立完成的,它们来自于一本叫做<学习JavaScript数据结构和算法>(第二版),人民邮电出版社出版的这本书.github ...

  5. Lintcode401 Kth Smallest Number in Sorted Matrix solution 题解

    [题目描述] Find the kth smallest number in at row and column sorted matrix. 在一个排序矩阵中找从小到大的第 k 个整数. 排序矩阵的 ...

  6. Python 内置的一些高效率函数用法

    1.  filter(function,sequence) 将sequence中的每个元素,依次传进function函数(可以自定义,返回的结果是True或者False)筛选,返回符合条件的元素,重组 ...

  7. Spring Data JPA 初体验

    一,JPA相关的概念 JPA概述 全称是:JavaPersistence API.是SUN公司推出的一套基于ORM的规范. Hibernate框架中提供了JPA的实现. JPA通过JDK 5.0注解或 ...

  8. dup和dup2应用实例(dup跟APUE有出入,close+dup=dup2?)

    dup/dup2函数 有时我们希望把标准输入重定向到一个文件,或者把标准输出重定向到一个网络连接. dup()与dup2()能对输入文件描述符进行重定向. 函数原型如下: dup函数创建一个新的文件描 ...

  9. C#通熟易懂观察者模式

    观察者模式(有时又被称为模型-视图(View)模式.源-收听者(Listener)模式或从属者模式)是软件设计模式的一种.将观察者(watcher)和被观察者(subject)完美分离. 这里讲一个场 ...

  10. Springboot2.0(Spring5.0)中个性化配置项目上的细节差异

    在一般的项目中,如果Spring Boot提供的Sping MVC不符合要求,则可以通过一个配置类(@Configuration)加上@EnableWebMvc注解来实现完全自己控制的MVC配置.但此 ...