在编程中我们经常会使用到Queue容器类,但是这些容器类不是线程安全的,因此concurrent包中Doug Lea大师
为我们准备了对应的线程安全的容器类;每一种容器类也满足了对应的使用场景;那些本文就是梳理这些并发的Queue容
器类及使用场景;
1、主要容器实现类:
ArrayBlockingQueue, DelayQueue, LinkedBlockingDeque, LinkedBlockingQueue, LinkedTransferQueue, PriorityBlockingQueue, SynchronousQueue
2、常用的BlockingQueue基本操作
插入操作:BlockingQueue实现的3和4
- add(E e) :往队列插入数据,当队列满时,插入元素时会抛出IllegalStateException异常;
- offer(E e):当往队列插入数据时,插入成功返回
true
,否则则返回false
。当队列满时不会抛出异常;- put:当阻塞队列容量已经满时,往阻塞队列插入数据的线程会被阻塞,直至阻塞队列已经有空余的容量可供使用;
- offer(E e, long timeout, TimeUnit unit):若阻塞队列已经满时,同样会阻塞插入数据的线程,直至阻塞队列已经有空余的地方,与put方法不同的是,该方法会有一个超时时间,若超过当前给定的超时时间,插入数据的线程会退出;
删除操作:BlockingQueue实现的3和4
- remove(Object o):从队列中删除数据,成功则返回
true
,否则为false
- poll:删除数据,当队列为空时,返回null;
- take():当阻塞队列为空时,获取队头数据的线程会被阻塞;
- poll(long timeout, TimeUnit unit):当阻塞队列为空时,获取数据的线程会被阻塞,另外,如果被阻塞的线程超过了给定的时长,该线程会退出
查看操作:
- element:获取队头元素,如果队列为空时则抛出NoSuchElementException异常;
- peek:获取队头元素,如果队列为空则抛出NoSuchElementException异常
3、常用BlockingQueue详解
①ArrayBlockingQueue 是由数组实现的有界阻塞队列,队列命令元素FIFO(先进先出);当队列容量满时放入元素操作会阻塞,当队列为null时获取一个元素也会阻塞;
默认情况下这种队列不能保证访问队列的公平性,这是为了提升吞吐量;确保公平性可以通过在构造函数中增加true设定;
②LinkedBlockingQueue 是用链表实现的有界阻塞队列,也是FIFO特性;具有更高的吞吐量; 为了防止迅速增加耗损大量的内容,通常创建对象时会指定其初始大小,未指定则为Interger.MAX_VALUE
③PriorityBlockingQueue
是一个支持优先级的无界阻塞队列,元素采用自然顺序进行排序,可以通过自定义类实现compareTo()方法来指定元素排序规则,或者初始化时通过构造器参数Comparator指定排序规则
④SynchrousQueue 不存储任何元素的阻塞队列,只有当其他线程删除数据才能插入数据,可以通过构造器参数来指定公平性;
⑤LinkedTransferQueue 由链表数据结构构成的无界阻塞队列,实现了TransferQueue接口,相比其他阻塞队列有以下不同方法:
transfer(E e)
如果当前有线程(消费者)正在调用take()方法或者可延时的poll()方法进行消费数据时,生产者线程可以调用transfer方法将数据传递给消费者线程。如果当前没有消费者线程的话,生产者线程就会将数据插入到队尾,直到有消费者能够进行消费才能退出;
tryTransfer(E e)
tryTransfer方法如果当前有消费者线程(调用take方法或者具有超时特性的poll方法)正在消费数据的话,该方法可以将数据立即传送给消费者线程,如果当前没有消费者线程消费数据的话,就立即返回false
。因此,与transfer方法相比,transfer方法是必须等到有消费者线程消费数据时,生产者线程才能够返回。而tryTransfer方法能够立即返回结果退出。
tryTransfer(E e,long timeout,imeUnit unit)</br>
与transfer基本功能一样,只是增加了超时特性,如果数据才规定的超时时间内没有消费者进行消费的话,就返回false
。
⑥LinkedBlockingDeque 基于链表数据结构的有界阻塞双端队列,在创建对象时为指定大小时,其默认大小为Integer.MAX_VALUE,
⑦DelayQueue 是一个存放实现Delayed接口的数据的无界阻塞队列,只有当数据对象的延时时间达到时才能插入到队列进行存储; 当前多有的数据都还没有达到创建时所指定的延时期,则队列没有队头,
并且线程通过poll等方法获取数据元素则返回null。所谓数据延时期满时,则是通过Delayed接口的getDelay(TimeUnit.NANOSECONDS)
来进行判定,如果该方法返回的是小于等于0则说明该数据元素的延时期已满。
4,ArrayBlockingQueue源码解析
使用案例:通过ArrayBlockingQueue实现消费者-生产者模式
package com.hezm.thread.day1.collections;
import java.util.concurrent.ArrayBlockingQueue;
public class BlockingDemo {
/***
* 利用 ArrayBlockingQueue 的添加已满会阻塞和获取已空会阻塞实现生产者-消费者模式
*
*/
ArrayBlockingQueue<String> ab = new ArrayBlockingQueue(10);
public void init() {
new Thread(()->{
try {
String data = ab.take(); //阻塞方式获取数据;
System.out.println("receive:" + data);
} catch (InterruptedException e) {
e.printStackTrace();
}
}).start();
}
public void addData(String data) {
ab.add(data);
try {
System.out.println("send:" + data);
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
public static void main(String[] args) {
BlockingDemo blockingDemo = new BlockingDemo();
for (int i = 0; i < 1000; i++) {
blockingDemo.addData("data:" + i);
blockingDemo.init();
}
}
}
运行结果:
send:data:0
send:data:1
receive:data:0
send:data:2
receive:data:1
send:data:3
receive:data:2
send:data:4
receive:data:3
send:data:5
receive:data:4
send:data:6
receive:data:5
send:data:7
receive:data:6
源码分析:
/**
* 关键元素: notEmpty 不为null唤醒 、 notFull 不为满唤醒
* 天然满足生产者-消费者:在为null的时候消费者阻塞等待,有值之后消费者被唤醒;
* 在队列满的时候生产者阻塞等待,不满的时候唤醒生产;
*
*add{
* offer(){
* 重入锁{ enqueue(e) 插入队列
* if (++putIndex == items.length) putIndex = 0; //当要插入的数组下标满了则重置为0;
* notEmpty.signal(); //去唤醒 take中的 notEmpty.await()
* }
* }
* }
*
*
* take{
* 重入锁中获取可中断的锁;
* notEmpty.await() 阻塞等待队列中设置进值
* dequeue() 出队列;{
* if (++takeIndex == items.length) takeIndex = 0; 获取队列的数组下标;
* itrs.elementDequeued(); // 维护迭代器中的内容;
* notFull.signal();
* }
* }
* remove() 移除
*/
//add() 往队列添加元素
public boolean offer(E e) {
checkNotNull(e);
final ReentrantLock lock = this.lock; //重入锁锁定资源
lock.lock();
try {
if (count == items.length)
return false;
else {
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,循环使用;
putIndex = 0;
count++;
notEmpty.signal(); //非null唤醒;
}
//take() 阻塞等待队列数据进行消费
public E take() throws InterruptedException {
final ReentrantLock lock = this.lock;
lock.lockInterruptibly(); //重入锁-可中断的锁获取
try {
while (count == 0)
notEmpty.await(); //当队列数为0时非null等待
return dequeue(); //出队列
} finally {
lock.unlock();
}
}
private E dequeue() {
final Object[] items = this.items;
@SuppressWarnings("unchecked")
E x = (E) items[takeIndex];
items[takeIndex] = null;
if (++takeIndex == items.length) //出队已满则重置读取下标为0;
takeIndex = 0;
count--;
if (itrs != null)
itrs.elementDequeued();
notFull.signal(); //未满队列等待
return x;
}