问题

(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. kmeans算法思想及其python实现

    第十章 利用k-均值聚类算法对未标注的数据进行分组 一.导语 聚类算法可以看做是一种无监督的分类方法,之所以这么说的原因是它和分类方法的结果相同,区别它的类别没有预先的定义.簇识别是聚类算法中经常使用 ...

  2. 3D Lut 电影级调色算法 附完整C代码

    在前面的文章,我提到过VSCO Cam 的胶片滤镜算法实现是3d lut. 那么3d lut  到底是个什么东西呢? 或者说它是用来做什么的? 长话短说,3d lut(全称 : 3D Lookup t ...

  3. PyQuery详解

    1.What is Pyquery? 答:灵活强大的网页解析库 2.安装: pip3 install pyquery 3.基本使用 初始化操作: 前言:在介绍之前小伙伴们我们先来了解下CSS的基本语法 ...

  4. C# 获取当前年份的周期,周期所在日期范围

    最近有一个项目要用到年份周期,用于数据统计图表展示使用,当中用到年份周期,以及年份周期所在的日期范围.当初设想通过已知数据来换算年份周期,经过搜索资料发现通过数据库SQL语句来做,反而更加复杂.现在改 ...

  5. Total Command使用笔记

    一.快键键(基于水晶2右)以下数字小键盘无效 Tab 左右窗口切换 Ctrl+d 进入工作目录ctrl+d+数字 指定目录alt+←/→ 后退/前进目录ctrl+\ 跳转到根目录Ctrl+b,不分层级 ...

  6. C语言 > 字符串和字符串函数

    输入 gets() 函数 : 1.gets() 从标准输入设备读取字符串,以回车结束读取,使用'\0'结尾,回车符'\n'被舍弃没有遗留在缓冲区. 2.可以用来输入带空格的字符串. 3.可以无限读取, ...

  7. codeforces 979D Kuro and GCD and XOR and SUM

    题意: 给出两种操作: 1.添加一个数字x到数组. 2.给出s,x,k,从数组中找出一个数v满足gcd(x,k) % v == 0 && x + v <= s && ...

  8. 基于.net的分布式系统限流组件

    在互联网应用中,流量洪峰是常有的事情.在应对流量洪峰时,通用的处理模式一般有排队.限流,这样可以非常直接有效的保护系统,防止系统被打爆.另外,通过限流技术手段,可以让整个系统的运行更加平稳.今天要与大 ...

  9. WBS 与 甘特图

    WBS:工作分解结构(Work Breakdown Structure) 创建WBS:创建WBS是把项目 交付成果和项目工作分解成较小的,更易于管理的组成部分的过程. WBS是项目管理重要的专业术语之 ...

  10. 使用非阻塞ServerSocketChannel、SocketChannel代替ServerSocket和Socket

    http://blog.csdn.net/timliang18601860/article/details/7104485