java并发程序——BlockingQueue
概述
BlockingQueue顾名思义‘阻塞的队列’,是指在:队列的读取行为被阻塞直到队列不为空时,队列的写入行为被阻塞直到队列不满时。BlockingQueue是java.util.concurrent工具包(jdk1.5版本引入,作者:Doug Lea)的重要基础工具,在ThreadPoolExcutor及tomcat等服务端容器中都有使用到。从代码层面剖析BlockingQueue的实现细节。
类图
对象存储
BlockingQueue中的对象可以存放在:数组、链表中,对应的就是ArrayBlockingQueue、LinkedBlockingQueue。
数组
ArrayBlockingQueue使用数组作为对象存储的数据结构,数组必须指定大小,故而ArrayBlockingQueue是有界队列。如下代码摘取了ArrayBlockingQueue中对象存取数据结构和核心方法的关键代码,从中可以窥见其实现原理。注意:该示例代码去掉了lock处理,故在多线程场景下,会存在并发问题。
除去类名上明显标有Linked字样的实现queue,其他queue通常都是用使用数组作为对象存储的数据结构。
/** The queued items */
final Object[] items; //使用数组作为对象存储的数据结构,所以是有界队列 /** items index for next take, poll, peek or remove */
int takeIndex; /** items index for next put, offer, or add */
int putIndex; /** Number of elements in the queue */
int count; /**
* Creates an {@code SimpleArrayQueue} with the given (fixed) capacity.
*
* @param capacity the capacity of this queue
* @throws IllegalArgumentException if {@code capacity < 1}
*/
public SimpleArrayQueue(int capacity) {
if (capacity <= 0)
throw new IllegalArgumentException();
this.items = new Object[capacity];
} /**
* Inserts element at current put position, advances
*/
private void enqueue(E x) {
// assert lock.getHoldCount() == 1;
// assert items[putIndex] == null;
final Object[] items = this.items;
items[putIndex] = x;
if (++putIndex == items.length) //在示例代码中,由于没有加上lock判定和计数器判定,如果队列已满,指针会循环寻址,队列中先入的元素可能会被后来的元素覆盖
putIndex = 0; //实际的ArrayBlockingQueue不会有该问题,这里的循环寻址配合dequeue的同样逻辑是为了保证队列的FIFO。
count++;
} /**
* Extracts element at current take position, advances
*/
private E dequeue() {
// assert lock.getHoldCount() == 1;
// assert items[takeIndex] != null;
final Object[] items = this.items;
@SuppressWarnings("unchecked")
E x = (E) items[takeIndex];
items[takeIndex] = null;
if (++takeIndex == items.length)
takeIndex = 0;
count--;
return x;
}
链表
LinkedBlockingQueue使用单向链表作为对象存储的数据结构,可以指定链表的容量,如果不指定,则使用Integer.MAXVALUE,因此,通常可以将LinkedBlockingQueue作为无界队列使用。
简单的使用单向链表实现queue的示例代码:
/**
* Head of linked list. 单向链表的头节点
* Invariant: head.item == null
*/
transient Node<E> head; /**
* Tail of linked list. 单向链表的尾节点
* Invariant: last.next == null
*/
private transient Node<E> last; /** The capacity bound, or Integer.MAX_VALUE if none */
private final int capacity; /** Current number of elements */
private final AtomicInteger count = new AtomicInteger(); /**
* Linked list node class 单向链表的节点定义
*/
static class Node<E> {
E item;
/**
* One of:
* - the real successor Node
* - this Node, meaning the successor is head.next
* - null, meaning there is no successor (this is the last node)
*/
Node<E> next;
Node(E x) { item = x; }
} public SimpleLinkedQueue(int capacity) {
this.capacity = capacity;
last = head = new Node<E>(null); //将头/尾节点均初始化为空节点
} /**
* Links node at end of queue. 入队
* @param node the node
*/
private void enqueue(Node<E> node) {
if(count.get() == capacity) {
throw new IllegalStateException("Queue full");
} else {
last = last.next = node;
count.incrementAndGet();
}
} /**
* Removes a node from head of queue. 出队
* @return the node
*/
private E dequeue() {
if(count.get()<=0) {
throw new IllegalStateException("Queue empty");
}
Node<E> h = head;
Node<E> first = h.next;
h.next = h; // help GC
head = first;
E x = first.item;
first.item = null;
count.getAndDecrement();
return x;
}
双链表
LinkedBlockingDeque使用双向链表存储元素,从而可以头/尾两个方向均可出队和入队。
/** Doubly-linked list node class */
static final class Node<E> {
/**
* The item, or null if this node has been removed.
*/
E item; /**
* One of:
* - the real predecessor Node
* - this Node, meaning the predecessor is tail
* - null, meaning there is no predecessor
*/
Node<E> prev; /**
* One of:
* - the real successor Node
* - this Node, meaning the successor is head
* - null, meaning there is no successor
*/
Node<E> next; Node(E x) {
item = x;
}
} /**
* Pointer to first node.
* Invariant: (first == null && last == null) ||
* (first.prev == null && first.item != null)
*/
transient Node<E> first; /**
* Pointer to last node.
* Invariant: (first == null && last == null) ||
* (last.next == null && last.item != null)
*/
transient Node<E> last; /** Number of items in the deque */
private transient int count; /** Maximum number of items in the deque */
private final int capacity; /**
* Creates a {@code LinkedBlockingDeque} with the given (fixed) capacity.
*
* @param capacity the capacity of this deque
* @throws IllegalArgumentException if {@code capacity} is less than 1
*/
public SimpleLinkedDeque(int capacity) {
if (capacity <= 0) throw new IllegalArgumentException();
this.capacity = capacity;
} /**
* Links node as first element, or returns false if full.
*/
private boolean linkFirst(Node<E> node) {
// assert lock.isHeldByCurrentThread();
if (count >= capacity)
return false;
Node<E> f = first;
node.next = f;
first = node;
if (last == null)
last = node;
else
f.prev = node;
++count;
return true;
} /**
* Links node as last element, or returns false if full.
*/
private boolean linkLast(Node<E> node) {
// assert lock.isHeldByCurrentThread();
if (count >= capacity)
return false;
Node<E> l = last;
node.prev = l;
last = node;
if (first == null)
first = node;
else
l.next = node;
++count;
return true;
} /**
* Removes and returns first element, or null if empty.
*/
private E unlinkFirst() {
// assert lock.isHeldByCurrentThread();
Node<E> f = first;
if (f == null)
return null;
Node<E> n = f.next;
E item = f.item;
f.item = null;
f.next = f; // help GC
first = n;
if (n == null)
last = null;
else
n.prev = null;
--count;
return item;
} /**
* Removes and returns last element, or null if empty.
*/
private E unlinkLast() {
// assert lock.isHeldByCurrentThread();
Node<E> l = last;
if (l == null)
return null;
Node<E> p = l.prev;
E item = l.item;
l.item = null;
l.prev = l; // help GC
last = p;
if (p == null)
first = null;
else
p.next = null;
--count;
return item;
}
对象存取
BlockingQueue提供了几种通用的对象存取方法,由其子类实现。注意:LinkedBlockingDeque是双向队列,故会提供头/尾两种方式的存/取方法,这里不做讲述。
以ArrayBlockingQueue的实现代码加以说明:
ArrayBlockingQueue.offer():
public boolean offer(E e) {
checkNotNull(e);
final ReentrantLock lock = this.lock;
lock.lock();
try {
if (count == items.length)
return false; //如果队列已满,则返回false,不抛出异常
else {
enqueue(e); //向队列尾部插入元素e
return true;
}
} finally {
lock.unlock();
}
}
ArrayBlockingQueue.offer(timeout),带超时的offer:
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(); //在lock锁定期间,该生产者线程可以被中断,好处是什么呢?
try {
while (count == items.length) {
if (nanos <= 0)
return false;
nanos = notFull.awaitNanos(nanos); //和offer(e)不一样,该方法会等待队列的notFull信号量,但等待时长不会超过设定的timeout时长。
}
enqueue(e);
return true;
} finally {
lock.unlock();
}
}
ArrayBlockingQueue.add():
public boolean add(E e) {
if (offer(e))
return true;
else
throw new IllegalStateException("Queue full"); //队列满,抛出异常
}
ArrayBlockingQueue.put():
public void put(E e) throws InterruptedException {
checkNotNull(e);
final ReentrantLock lock = this.lock;
lock.lockInterruptibly();
try {
while (count == items.length)
notFull.await(); //队列满时,生产者线程阻塞等待,直到该队列被消费者线程take后,notFull condition被signal触发
enqueue(e);
} finally {
lock.unlock();
}
}
读取对象的的操作方法原理和上面写入对象的类似,不再赘述。
锁
ArrayBlockingQueue只有一个锁,对应两个Condition,count计数器也不是Atomic的;
LinkedBlockQueue有两个锁,一个put,一个take,也有两个Condition,所以put和take之间是不互斥的,但是在遍历和remov、clear操作时需要同时加两把锁;
理论上,LinkedBlockQueue的put / take性能要好于ArrayBlockingQueue。
一个问题:是否可以考虑也用两把锁实现ArrayBlockingQueue呢?
特殊队列
SynchronousQueue
SynchronousQueue是一种特殊的队列,其内部实现的Node,不是存储任务对象,而是存放生产者或者消费者线程,或者说这个队列的容量永远是零,因为他规定:一个生产线程,当它生产产品(即put的时候),如果当前没有人想要消费产品(即当前没有线程执行take),此生产线程必须阻塞,等待一个消费线程调用take操作,take操作将会唤醒该生产线程,同时消费线程会获取生产线程的产品(即数据传递),这样的一个过程称为一次配对过程(当然也可以先take后put,原理是一样的)。
其模型参考:java并发之SynchronousQueue实现原理
源码分析-SynchronousQueue 这篇文章讲的特别好。
DelayQueue
DelayQueue 对元素进行持有直到一个特定的延迟到期。
能放入DelayQueue的元素都需要实现Delayed接口,Delayed接口实现了Comparable接口,用处在于在入队时,会根据放入元素的getDelay()返回的延迟时间进行排序,决定元素的排序,以便根据延迟时间从近及远有序出队。
DelayQueue队列内部实际直接持有了一个PriorityQueue队列,将按照元素的延迟时间长短作为优先级,到期时间最近的元素先出队。
public class DelayQueue<E extends Delayed> extends AbstractQueue<E>
implements BlockingQueue<E> { private final PriorityQueue<E> q = new PriorityQueue<E>();
PriorityBlockingQueue
具有优先级的无界队列,元素存放在数组中,队列元素必须实现Comparable接口,入队时需要根据元素的compareTo()决定优先级。其他操作同ArrayBlockingQueue。
private static <T> void siftUpComparable(int k, T x, Object[] array) {
Comparable<? super T> key = (Comparable<? super T>) x;
while (k > 0) {
int parent = (k - 1) >>> 1;
Object e = array[parent];
if (key.compareTo((T) e) >= 0)
break;
array[k] = e;
k = parent;
}
array[k] = key;
}
ConcurrentLinkedQueue vs BlockingQueue
ConcurrentLinkedQueue是非阻塞的,借助CAS操作实现线程安全,是性能最高的并发队列;
BlockingQueue是阻塞的队列,提供了阻塞式的put/take api,是天然的实现 consumer/producer模式的队列。当然他也提供了非阻塞式的api,如offer/poll,add/remove。
ConcurrentLinkedQueue的.size() 是要遍历一遍集合的,很慢的,所以尽量要避免用size,如果判断队列是否为空最好用isEmpty()而不是用size来判断.
参考:linkedblockingqueue-vs-concurrentlinkedqueue
聊聊并发(六)ConcurrentLinkedQueue的实现原理分析
问题
CLQ 和 LBQ的典型应用场景?
java并发程序——BlockingQueue的更多相关文章
- Java并发编程——BlockingQueue
简介 BlockingQueue很好的解决了多线程中,如何高效安全"传输"数据的问题.通过这些高效并且线程安全的队列类,为我们快速搭建高质量的多线程程序带来极大的便利. 阻塞队列是 ...
- java并发程序和共享对象实用策略
java并发程序和共享对象实用策略 在并发程序中使用和共享对象时,可以使用一些实用的策略,包括: 线程封闭 只读共享.共享的只读对象可以由多个线程并发访问,但任何线程都不能修改它.共享的只读对象包括不 ...
- 解读 java 并发队列 BlockingQueue
点击添加图片描述(最多60个字)编辑 今天呢!灯塔君跟大家讲: 解读 java 并发队列 BlockingQueue 最近得空,想写篇文章好好说说 java 线程池问题,我相信很多人都一知半解的,包括 ...
- Java并发编程--BlockingQueue
概述 BlockingQueue支持两个附加操作的Queue:1)当Queue为空时,获取元素线程被阻塞直到Queue变为非空:2)当Queue满时,添加元素线程被阻塞直到Queue不满.Blocki ...
- java并发程序——并发容器
概述 java cocurrent包提供了很多并发容器,在提供并发控制的前提下,通过优化,提升性能.本文主要讨论常见的并发容器的实现机制和绝妙之处,但并不会对所有实现细节面面俱到. 为什么JUC需要提 ...
- 并发编程(九)—— Java 并发队列 BlockingQueue 实现之 LinkedBlockingQueue 源码分析
LinkedBlockingQueue 在看源码之前,通过查询API发现对LinkedBlockingQueue特点的简单介绍: 1.LinkedBlockingQueue是一个由链表实现的有界队列阻 ...
- Java 并发队列 BlockingQueue
BlockingQueue 开篇先介绍下 BlockingQueue 这个接口的规则,后面再看其实现. 首先,最基本的来说, BlockingQueue 是一个先进先出的队列(Queue),为什么说是 ...
- java并发程序——Excutor
概述 Excutor这个接口用的不多,但是ThreadPoolExcutor这个就用的比较多了,ThreadPoolExcutor是Excutor的一个实现.Excutor体系难点没有,大部分的关键点 ...
- 并发编程(八)—— Java 并发队列 BlockingQueue 实现之 ArrayBlockingQueue 源码分析
开篇先介绍下 BlockingQueue 这个接口的规则,后面再看其实现. 阻塞队列概要 阻塞队列与我们平常接触的普通队列(LinkedList或ArrayList等)的最大不同点,在于阻塞队列的阻塞 ...
随机推荐
- volatile关键字和synchronized关键字
volatile关键字: 可以用来修饰字段(成员变量),就是告知程序任何对该变量的访问均需要从共享内存中获取,而对它的改变必须同步刷新回共享内存,它能保证所有线程对变量访问的可见性. synchron ...
- javascript继承--原型链的 继承
作者的话:原型链是JavaScript中相当重要的一个知识点,这里我使用了函数结构图,来帮助我更好的理解 /* 原型链继承方式: 通过改变一个对象的原型对象的指向来继承另一个对象 原理: 我们知道,一 ...
- 模拟一个shuffle
之所以会想到写这么一个shuffle的例子,是因为一个需求:我需要把一个有序数组中的数据随机的打散.java代码如下, public void shuffle() { int[] arr = {1,2 ...
- 如何下载github项目中的部分文件(文件夹)
https://minhaskamal.github.io/DownGit/#/home 将你要下载的链接放进去即可.
- KMP算法C语言实现。弄了好久才搞好。。。
我的这个算法中数组的第一位没有像教材中那样用来存数组的大小,所以会有些许的不同. ...
- CA/B Forum: SSL证书最长有效期最终被定为两年
这项新规将在2018年实施...... 随着CAB Forum第193号投票的通过,SSL行业将拥有更新更短的最长SSL证书有效期. 作为SSL行业的风向标,CAB Forum制定过许多行业规则,及规 ...
- Error--解决使用Application Loader提交ipa包审核时的报错:ERROR ITMS-90168: "The binary you uploaded was invalid."
在提交iTunes Connect审核时,使用Application Loader提交ipa包时报错:ERROR ITMS-90168: "The binary you uploaded w ...
- 实现五种分组加密模式ECB,CBC,CFB,OFB,CTR
没什么好说的,简单无脑! #include<iostream>using namespace std; int ECB(){ int duan[4]; int messageLen = 1 ...
- Spring+Redis(keyspace notification)实现定时任务(订单过期自动关闭)
1.起因 最近公司项目要做订单超期未支付需自动关闭,首先想到的是用spring的定时器(@Schedule),结果领导举各种例子说会影响性能,只能作罢.后来想能不能基于redis实现, 学习(baid ...
- iOS开发tips-神奇的UITableView
概述 UITableView是iOS开发中使用频率最高的UI控件,在前面的文章中对于UITableView的具体用法有详细的描述,今天主要看一些UITableView开发中的常见一些坑,这些坑或许不深 ...