java中所有队列都继承至java.util.Queue接口,该接口定义了以下三组方法:
方法名 | 抛出异常 | 返回特殊值 |
---|---|---|
插入 | add(e) | offer(e) |
移除 | remove() | poll() |
检查 | element() | peek() |
Java提供的线程安全的Queue可以分为阻塞队列和非阻塞队列,使用阻塞算法的队列可以用一个锁(入队和出队用同一把锁)或两个锁(入队和出队用不同的锁)等方式来实现,而非阻塞的实现方式则可以使用循环CAS的方式来实现, 其中阻塞队列的典型例子是BlockingQueue,非阻塞队列的典型例子是ConcurrentLinkedQueue。
BlockingQueue 对插入操作、移除操作、获取元素操作提供了四种不同的方法用于不同的场景中使用:
– | 抛出异常 | 特殊值 | 阻塞 | 超时 |
---|---|---|---|---|
插入 | add(e) | offer(e) | put(e) | offer(e, time, unit) |
移除 | remove() | poll() | take() | poll(time, unit) |
检查 | element() | peek() | 不可用 | 不可用 |
从上表可以很明显看出每个方法的作用,这个不用多说。我想说的是:
add(e) remove() element()
方法不会阻塞线程。当不满足约束条件时,会抛出IllegalStateException 异常。例如:当队列被元素填满后,再调用add(e),则会抛出异常。offer(e) poll() peek()
方法即不会阻塞线程,也不会抛出异常。例如:当队列被元素填满后,再调用offer(e),则不会插入元素,函数返回false。put(e) take()
方法。 当不满足约束条件时,会阻塞线程。其实质可以用一个锁(入队和出队共享一把锁)来实现线程安全。以ArrayBlockQueue源码中put(e)/take()源码如下:
/**
* Inserts the specified element at the tail of this queue, waiting
* for space to become available if the queue is full.
*
* @throws InterruptedException {@inheritDoc}
* @throws NullPointerException {@inheritDoc}
*/
public void put(E e) throws InterruptedException {
checkNotNull(e);
final ReentrantLock lock = this.lock;
lock.lockInterruptibly();
try {
while (count == items.length)
notFull.await();
enqueue(e);
} finally {
lock.unlock();
}
}
/**
* Inserts element at current put position, advances, and signals.
* Call only when holding lock.
*/
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)
putIndex = 0;
count++;
notEmpty.signal();
}
public E take() throws InterruptedException {
final ReentrantLock lock = this.lock;
lock.lockInterruptibly();
try {
while (count == 0)
notEmpty.await();
return dequeue();
} finally {
lock.unlock();
}
}
/**
* Extracts element at current take position, advances, and signals.
* Call only when holding lock.
*/
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--;
if (itrs != null)
itrs.elementDequeued();
notFull.signal();
return x;
}
BlockingQueue是个接口,有如下常用实现类:
使用示例:
ArrayBlockQueue使用(生产者-消费者):https://www.jianshu.com/p/b1408e3e3bb4
ConcurrentLinkedQueue是一个基于链接节点的无界线程安全队列,它采用先进先出的规则对节点进行排序,当我们添加一个元素的时候,它会添加到队列的尾部,当我们获取一个元素时,它会返回队列头部的元素。基于CAS的“wait-free”(常规无等待)来实现,CAS并不是一个算法,它是一个CPU直接支持的硬件指令,这也就在一定程度上决定了它的平台相关性。
再通过源码来详细分析下它是如何使用循环CAS的方式来入队的(JDK1.8)
public boolean offer(E e) {
checkNotNull(e);
//创建入队节点
final Node newNode = new Node(e);
//t为tail节点,p为尾节点,默认相等,采用失败即重试的方式,直到入队成功
for (Node t = tail, p = t;;) {
//获得p的下一个节点
Node q = p.next;
// 如果下一个节点是null,也就是p节点就是尾节点
if (q == null) {
//将入队节点newNode设置为当前队列尾节点p的next节点
if (p.casNext(null, newNode)) {
//判断tail节点是不是尾节点,也可以理解为如果插入结点后tail节点和p节点距离达到两个结点
if (p != t)
//如果tail不是尾节点则将入队节点设置为tail。
// 如果失败了,那么说明有其他线程已经把tail移动过
casTail(t, newNode);
return true;
}
}
// 如果p节点等于p的next节点,则说明p节点和q节点都为空,表示队列刚初始化,所以返回 head节点
else if (p == q)
p = (t != (t = tail)) ? t : head;
else
//p有next节点,表示p的next节点是尾节点,则需要重新更新p后将它指向next节点
p = (p != t && t != (t = tail)) ? t : q;
}
}
public E poll() {
// 设置起始点
restartFromHead:
for (;;) {
//p表示head结点,需要出队的节点
for (Node h = head, p = h, q;;) {
//获取p节点的元素
E item = p.item;
//如果p节点的元素不为空,使用CAS设置p节点引用的元素为null
if (item != null && p.casItem(item, null)) {
if (p != h) // hop two nodes at a time
//如果p节点不是head节点则更新head节点,也可以理解为删除该结点后检查head是否与头结点相差两个结点,如果是则更新head节点
updateHead(h, ((q = p.next) != null) ? q : p);
return item;
}
//如果p节点的下一个节点为null,则说明这个队列为空,更新head结点
else if ((q = p.next) == null) {
updateHead(h, p);
return null;
}
//结点出队失败,重新跳到restartFromHead来进行出队
else if (p == q)
continue restartFromHead;
else
p = q;
}
}
}
ConcurrentLinkedQueue 的非阻塞算法实现主要可概括为下面几点:
使用示例:
Java ConcurrentLinkedQueue队列线程安全操作:https://yq.aliyun.com/articles/615890/
参考:
解读 Java 并发队列 BlockingQueue:https://javadoop.com/post/java-concurrent-queue
Java多线程高并发学习笔记——阻塞队列:https://cloud.tencent.com/developer/article/1090012
Java线程安全队列:https://www.jianshu.com/p/ad6ef76e067a
第二十一章、java线程安全队列:https://www.jianshu.com/p/04aeb0088dec
java并发之ConcurrentLinkedQueue:https://www.jianshu.com/p/24516e7853d1
ConcurrentLinkedQueue的实现原理和源码分析:https://www.jianshu.com/p/26d9745614dd
Java线程(十):CAS:https://www.kancloud.cn/digest/java-thread/107465