10.并发包阻塞队列之ArrayBlockingQueue
jdk1.7.0_79
上一节中对并发包中的非阻塞队列ConcurrentLinkedQueue的入队、出队做了一个简要的分析,本文将对并发包中的阻塞队列做一个简要分析。
Java并发包中的阻塞队列一共7个,当然他们都是线程安全的。
ArrayBlockingQueue:一个由数组结构组成的有界阻塞队列。
LinkedBlockingQueue:一个由链表结构组成的有界阻塞队列。
PriorityBlockingQueue:一个支持优先级排序的无界阻塞队列。
DealyQueue:一个使用优先级队列实现的无界阻塞队列。
SynchronousQueue:一个不存储元素的阻塞队列。
LinkedTransferQueue:一个由链表结构组成的无界阻塞队列。
LinkedBlockingDeque:一个由链表结构组成的双向阻塞队列。(摘自《Java并发编程的艺术》)
在本文对ArrayBlockingQueue阻塞队列做一个简要解析
对于ArrayLinkedQueue,放眼看过去其安全性的保证是由ReentrantLock保证的,有关ReentrantLock的解析可参考《5.Lock接口及其实现ReentrantLock》,在下文我也会适当的提及。
首先来查看其构造函数:
|
构造方法 |
|
|
public ArrayBlockingQueue(int capacity) |
构造指定大小的有界队列 |
|
public ArrayBlockingQueue(int capacity, boolean fair) |
构造指定大小的有界队列,指定为公平或非公平锁 |
|
public ArrayBlockingQueue(int capacity, boolean fair, Collection<? extends E> c) |
构造指定大小的有界队列,指定为公平或非公平锁,指定在初始化时加入一个集合 |
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);//初始化ReentrantLock重入锁,出队入队拥有这同一个锁
notEmpty = lock.newCondition;//初始化非空等待队列,有关Condition可参考《6.类似Object监视器方法的Condition接口》
notFull = lock.newCondition;//初始化非满等待队列
}
public ArrayBlockingQueue(int capacity, boolean fair, Collecation<? extends E> c) {
this(capacity, fair);
final ReentrantLock lock = this.lock;
lock.lock();//注意在这个地方需要获得锁,这为什么需要获取锁的操作呢?
try {
int i = 0;
try {
for (E e : c) {
checkNotNull(e);
item[i++] = e;//将集合添加进数组构成的队列中
}
} catch (ArrayIndexOutOfBoundsException ex) {
throw new IllegalArgumentException();
}
count = i;//队列中的实际数据数量
putIndex = (i == capacity) ? 0 : i;
} finally {
lock.unlock();
}
}
在第15行,源码里给了一句注释: Lock only for visibility, not mutual exclusion。这句话的意思就是给出,这个锁的操作并不是为了互斥操作,而是保证其可见性。线程T1是实例化ArrayBlockingQueue对象,T2是对实例化的ArrayBlockingQueue对象做入队操作(当然要保证T1和T2的执行顺序),如果不对它进行加锁操作(加锁会保证其可见性,也就是写回主存),T1的集合c有可能只存在T1线程维护的缓存中,并没有写回主存,T2中实例化的ArrayBlockingQueue维护的缓存以及主存中并没有集合c,此时就因为可见性造成数据不一致的情况,引发线程安全问题。
以下是ArrayBlockingQueue的一些出队入队操作。
队列元素的插入
|
|
抛出异常 |
返回值(非阻塞) |
一定时间内返回值 |
返回值(阻塞) |
|
插入 |
add(e)//队列未满时,返回true;队列满则抛出IllegalStateException(“Queue full”)异常——AbstractQueue |
offer(e)//队列未满时,返回true;队列满时返回false。非阻塞立即返回。 |
offer(e, time, unit)//设定等待的时间,如果在指定时间内还不能往队列中插入数据则返回false,插入成功返回true。 |
put(e)//队列未满时,直接插入没有返回值;队列满时会阻塞等待,一直等到队列未满时再插入。 |
//ArrayBlockingQueue#add
public boolean add(E e) {
return super.add(e);
}
//AbstractQueue#add,这是一个模板方法,只定义add入队算法骨架,成功时返回true,失败时抛出IllegalStateException异常,具体offer实现交给子类实现。
public boolean add(E e) {
if (offer(e))//offer方法由Queue接口定义
return true;
else
throw new IllegalStateException();
}
//ArrayBlockingQueue#offer,队列未满时返回true,满时返回false
public boolean offer(E e) {
checkNotNull(e);//检查入队元素是否为空
final ReentrantLock lock = this.lock;
lock.lock();//获得锁,线程安全
try {
if (count == items.length)//队列满时,不阻塞等待,直接返回false
return false;
else {
insert(e);//队列未满,直接插入
return true;
}
} finally {
lock.unlock();
}
}
//ArrayBlockingQueue#insert
private void insert(E e) {
items[putIndex] = x;
putIndex = inc(putIndex);
++count;
notEmpty.signal();//唤醒非空等待队列中的线程,有关Condition可参考《6.类似Object监视器方法的Condition接口》
}
在这里有几个ArrayBlockingQueue成员变量。items即队列的数组引用,putIndex表示等待插入的数组下标位置。当items[putIndex] = x将新元素插入队列中后,调用inc将数组下标向后移动,如果队列满则将putIndex置为0:
//ArrayBlockingQueue#inc
private int inc(int i) {
return (++i == items.length) ? 0 : i;
}
接着解析下put方法,阻塞插入队列,当队列满时不会返回false,也不会抛出异常,而是一直阻塞等待,直到有空位可插入,但它可被中断返回。
//ArrayBlockingQueue#put
public void put(E e) throws InterruptedException {
checkNotNull(e);//同样检查插入元素是否为空
final ReentrantLock lock = this.lock;
lock.lockInterruptibly();//这里并没有调用lock方法,而是调用了可被中断的lockInterruptibly,该方法可被线程中断返回,lock不能被中断返回。
try {
while (count == items.length)
notFull.await();//当队列满时,使非满等待队列休眠
insert(e);//此时表示队列非满,故插入元素,同时在该方法里唤醒非空等待队列
} finally {
lock.unlock();
}
}
队列元素的删除
|
抛出异常 |
返回值(非阻塞) |
一定时间内返回值 |
返回值(阻塞) |
|
remove()//队列不为空时,返回队首值并移除;队列为空时抛出NoSuchElementException()异常——AbstractQueue |
poll()//队列不为空时返回队首值并移除;队列为空时返回null。非阻塞立即返回。 |
poll(time, unit)//设定等待的时间,如果在指定时间内队列还未孔则返回null,不为空则返回队首值 |
take(e)//队列不为空返回队首值并移除;当队列为空时会阻塞等待,一直等到队列不为空时再返回队首值。 |
//AbstractQueue#remove,这也是一个模板方法,定义删除队列元素的算法骨架,队列中元素时返回具体元素,元素为空时抛出异常,具体实现poll由子类实现,
public E remove() {
E x = poll();//poll方法由Queue接口定义
if (x != null)
return x;
else
throw new NoSuchElementException();
}
//ArrayBlockingQueue#poll,队列中有元素时返回元素,不为空时返回null
public E poll() {
final ReentrantLock lock = this.lock;
lock.lock();
try {
return (count == 0) ? null : extract();
} finally {
lock.unlock();
}
}
//ArrayBlockingQueue#extract
private E extract() {
final Object[] items = this.items;
E x = this.<E>cast(items[takeIndex]);//移除队首元素
items[takeIndex] = null;//将队列数组中的第一个元素置为null,便于GC回收
takeIndex = inc(takeIndex);
--count;
notFull.signal();//唤醒非满等待队列线程
return x;
}
对比add和offer方法,理解了上两个方法后remove和poll实际不难理解,同理在理解了put阻塞插入队列后,对比take阻塞删除队列元素同样也很好理解。
//ArrayBlockQueue#take
public E take() throws InterruptedException {
final ReentrantLock lock = this.lock();
lock.lockInterrupted();//这里并没有调用lock方法,而是调用了可被中断的lockInterruptibly,该方法可被线程中断返回,lock不能被中断返回。
try {
while (count == 0)//队列元素为空
notEmpty.await();//非空等待队列休眠
return extract();//此时表示队列非空,故删除元素,同时在里唤醒非满等待队列
} finally {
lock.unlock();
}
}
最后一个方法size。
public int size() {
final ReentrantLock lock = this.lock;
lock.lock();
try {
return count;
} finally {
lock.unlock();
}
}
可以看到ArrayBlockingQueue队列的size方法,是直接返回的count变量,它不像ConcurrentLinkedQueue,ConcurrentLinkedQueue的size则是每次会遍历这个队列,故ArrayBlockingQueue的size方法比ConcurrentLinkedQueue的size方法效率高。而且ConcurrentLinkedQueue的size方法并没有加锁!也就是说很有可能其size并不准确,这在它的注释中说明了ConcurrentLinkedQueue的size并没有多大的用处。
10.并发包阻塞队列之ArrayBlockingQueue的更多相关文章
- 并发包阻塞队列之ArrayBlockingQueue
并发包阻塞队列之ArrayBlockingQueue jdk1.7.0_79 上一节中对并发包中的非阻塞队列ConcurrentLinkedQueue的入队.出队做了一个简要的分析,本文将对并发 ...
- 11.并发包阻塞队列之LinkedBlockingQueue
在上文<10.并发包阻塞队列之ArrayBlockingQueue>中简要解析了ArrayBlockingQueue部分源码,在本文中同样要介绍的是Java并发包中的阻塞队列LinkedB ...
- Java并发之BlockingQueue 阻塞队列(ArrayBlockingQueue、LinkedBlockingQueue、DelayQueue、PriorityBlockingQueue、SynchronousQueue)
package com.thread.test.thread; import java.util.Random; import java.util.concurrent.*; /** * Create ...
- java并发包——阻塞队列BlockingQueue及源码分析
一.摘要 BlockingQueue通常用于一个线程在生产对象,而另外一个线程在消费这些对象的场景,例如在线程池中,当运行的线程数目大于核心的线程数目时候,经常就会把新来的线程对象放到Blocking ...
- java阻塞队列之ArrayBlockingQueue
在Java的java.util.concurrent包中定义了和多线程并发相关的操作,有许多好用的工具类,今天就来看下阻塞队列.阻塞队列很好的解决了多线程中数据的安全传输问题,其中最典型的例子就是客园 ...
- 深入浅出 Java Concurrency (25): 并发容器 part 10 双向并发阻塞队列 BlockingDeque[转]
这个小节介绍Queue的最后一个工具,也是最强大的一个工具.从名称上就可以看到此工具的特点:双向并发阻塞队列.所谓双向是指可以从队列的头和尾同时操作,并发只是线程安全的实现,阻塞允许在入队出队不满足条 ...
- 9.并发包非阻塞队列ConcurrentLinkedQueue
jdk1.7.0_79 队列是一种非常常用的数据结构,一进一出,先进先出. 在Java并发包中提供了两种类型的队列,非阻塞队列与阻塞队列,当然它们都是线程安全的,无需担心在多线程并发环境所带来的不可 ...
- 阻塞队列---ArrayBlockingQueue,LinkedBlockingQueue,DelayQueue源码分析
阻塞队列和非阻塞队列阻塞队列和非阻塞队列的区别:阻塞队列可以自己阻塞,非阻塞队列不能自己阻塞,只能使用队列wait(),notify()进行队列消息传送.而阻塞队列当队列里面没有值时,会阻塞直到有值输 ...
- Java中的阻塞队列-ArrayBlockingQueue(一)
最近在看一些java基础的东西,看到了队列这章,打算对复习的一些知识点做一个笔记,也算是对自己思路的一个整理,本章先聊聊java中的阻塞队列 参考文章: http://ifeve.com/java-b ...
随机推荐
- java设计模式--基础思想总结--父类引用操作对象
看设计模式的相关书籍也有一段时间了,一开始其实是抱着作为java三大框架的基础知识储备来学习的,不过到后来,才发现,在设计模式的一些准则装饰下,java的面向对象威力才真正地体现出来,后面的将会陆续地 ...
- 蓝桥杯-加法变乘法-java
/* (程序头部注释开始) * 程序的版权和版本声明部分 * Copyright (c) 2016, 广州科技贸易职业学院信息工程系学生 * All rights reserved. * 文件名称: ...
- linux 内核的spinlock
spinlock设计成了三层,第一层是接口,第二层raw实现层,第三层是CPU平台层.在第二层raw实现层提供了两个分支,分别是单CPU和多CPU(核).第三层是不同CPU的锁操作实现.raw层除了调 ...
- SSM框架搭建——我的第一个SSM项目
转载自:http://blog.csdn.net/tmaskboy/article/details/51464791 作者使用MyEclipse 2014版本 本博客所编写程序源码为: http:// ...
- MySQL索引及查询优化总结
欢迎大家关注腾讯云技术社区-博客园官方主页,我们将持续在博客园为大家推荐技术精品文章哦~ 作者:谢庆玲 文章<MySQL查询分析>讲述了使用MySQL慢查询和explain命令来定位mys ...
- python3 selenium 随机选择同一类型下的某一个元素
使用场景: 如上图所示,有时候,我们测试的时候,不会每个方向都选择一遍,也不能每次都选择一个方向,这个时候就需要每次运行用例的时候,随机选择一个方向来测试 使用方法: random.randint() ...
- 一个大数据平台省了20个IT人力——敦奴数据平台建设案例分享
认识敦奴 敦奴集团创立于1987年,主营服装.酒店.地产,总部位于中国皮都-海宁.浙江敦奴联合实业股份有限公司(以下简称"敦奴")是一家集开发.设计.生产.销售于一体的大型专业服装 ...
- 解决ajax跨域的方法原理详解之Cors方法
1.神马是跨域(Cross Domain) 对于端口和协议的不同,只能通过后台来解决. 一句话:同一个ip.同一个网络协议.同一个端口,三者都满足就是同一个域,否则就是 跨域问题了.而为什么开 ...
- 版本管理工具SVN学习(一):简单的SVN命令,兼对比Git
新公司用SVN来管理代码,而且公司自己搭建了SVN服务器,所以要学习下SVN的相关命令.服务器搭建等技能知识.上家公司是用Git管理代码,而且代码托管在git@oschina上,自然不用操心Git服务 ...
- Kafka权威指南——broker的常用配置
前面章节中的例子,用来作为单个节点的服务器示例是足够的,但是如果想要把它应用到生产环境,就远远不够了.在Kafka中有很多参数可以控制它的运行和工作.大部分的选项都可以忽略直接使用默认值就好,遇到一些 ...