ArrayBlockingQueue

ArrayBlockingQueue 能解决什么问题?什么时候使用 ArrayBlockingQueue?

1)ArrayBlockingQueue 是底层由数组支持的有界阻塞队列,队列按照 FIFO 顺序对元素进行排序,读取元素从头部开始,写入元素追加到队列尾部。
2)当容量超出限制时,put 写入操作将被阻塞;当队列为空时,take 读取操作将被阻塞。
3)offer 操作支持 fast-fail 写入和超时写入,poll 支持 fast-fail 读取和超时读取。
4)ArrayBlockingQueue 支持对等待的生产者线程和消费者线程进行排序的可选公平策略,默认情况下是非公平的,公平模式下会降低其吞吐量。
5)ArrayBlockingQueue 使用 ReentrantLock 来保证线程安全。

如何使用 ArrayBlockingQueue?

1)生成者消费者并发读写的场景下,并且生产者和消费者基本平衡。

使用 ArrayBlockingQueue 有什么风险?

1)读写操作使用相同的互斥锁,不支持并发读写,相对于 LinkedBlockingQueue 而言性能不是特别高。
2)消费者和生产者不平衡时,高并发读写会阻塞操作线程导致大量线程等待,浪费资源。

ArrayBlockingQueue 核心操作的实现原理?

  • 创建实例
    /** 底层存储元素的对象数组 */
final Object[] items; /** 下一次 take, poll, peek or remove 操作的目标元素索引 */
int takeIndex; /** 下一次 put, offer, or add 操作的目标元素索引*/
int putIndex; /** 队列中的已有元素个数 */
int count; /*
* Concurrency control uses the classic two-condition algorithm
* found in any textbook.
*/ /** 保证线程安全访问的可重入互斥锁 */
final ReentrantLock lock; /** 读取元素时,队列为空,则在该条件上阻塞 */
private final Condition notEmpty; /** 写入元素时,队列已满,则在该条件上阻塞 */
private final Condition notFull; /**
* 创建最大容量为 capacity 的非公平有界阻塞队列。
*/
public ArrayBlockingQueue(int capacity) {
this(capacity, false);
} /**
* 1)fair=true,创建最大容量为 capacity 的公平有界阻塞队列。
* 2)fair=true,创建最大容量为 capacity 的非公平有界阻塞队列。
*/
public ArrayBlockingQueue(int capacity, boolean fair) {
if (capacity <= 0) {
throw new IllegalArgumentException();
}
this.items = new Object[capacity];
// 通过 ReentrantLock 来保证多线程并发安全性
lock = new ReentrantLock(fair);
// 读取元素时队列为空,则在非空添加上阻塞等待
notEmpty = lock.newCondition();
// 写入元素时队列已满,则在非满条件上阻塞等待
notFull = lock.newCondition();
}
  • 写入元素:获取互斥锁,进入条件队列后释放互斥锁,节点被转移到同步队列中并获取互斥锁,put 操作完毕释放互斥锁。
    /**
* 将元素添加到队列尾部,如果队列已满,则阻塞当前线程。
* 可响应线程中断
*/
@Override
public void put(E e) throws InterruptedException {
Objects.requireNonNull(e);
// 读取互斥锁
final ReentrantLock lock = this.lock;
// 可中断地获取互斥锁
lock.lockInterruptibly();
try {
// 如果队列已满
while (count == items.length) {
/**
* 则将当前线程加入非满的条件队列,并阻塞等待唤醒,
* 当前线程被唤醒后会再次尝试将目标元素加入到队列中,可以被重复阻塞。
*/
notFull.await();
}
// 将元素添加到队列尾部
enqueue(e);
} finally {
// 释放锁
lock.unlock();
}
} AbstractQueuedSynchronizer#
/**
* 当前线程在指定条件上阻塞等待
*/
public final void await() throws InterruptedException {
// 线程被设置了中断标识
if (Thread.interrupted()) {
throw new InterruptedException();
}
// 创建一个新的节点并将其加入到条件队列尾部
final Node node = addConditionWaiter();
// 尝试释放互斥锁,并返回同步状态
final int savedState = fullyRelease(node);
int interruptMode = 0;
// 如果新建节点在条件队列中
while (!isOnSyncQueue(node)) {
// 阻塞当前线程,等待被 signal 唤醒或被其他线程中断
LockSupport.park(this);
// 读取线程的中断状态,如果未被中断,则退出循环
if ((interruptMode = checkInterruptWhileWaiting(node)) != 0) {
break;
}
}
/**
* 1)在独占、线程不可中断的模式下获取锁,如果线程被中断
* && 是在被唤醒后中断,则更新中断模式。
*/
if (acquireQueued(node, savedState) && interruptMode != ConditionObject.THROW_IE) {
interruptMode = ConditionObject.REINTERRUPT;
}
// 当前节点存在后置节点
if (node.nextWaiter != null) {
// 踢除无效节点
unlinkCancelledWaiters();
}
// 如果中断模式不是 0
if (interruptMode != 0) {
reportInterruptAfterWait(interruptMode);
}
} private Node addConditionWaiter() {
// 互斥锁没有被当前线程持有
if (!isHeldExclusively()) {
// 则抛出 IllegalMonitorStateException 异常
throw new IllegalMonitorStateException();
}
// 读取最后一个等待节点
Node t = lastWaiter;
// 条件队列中的最后一个节点已经被取消
if (t != null && t.waitStatus != Node.CONDITION) {
// 踢除所有被取消的节点
unlinkCancelledWaiters();
// 重新读取条件队列尾节点
t = lastWaiter;
}
// 创建一个条件节点,waitStatus=-2
final Node node = new Node(Node.CONDITION);
// 尾节点为空,表示当前节点是第一个有效的节点
if (t == null) {
// 设置头节点
firstWaiter = node;
} else {
// 更新旧尾节点的后置节点,即将当前节点链接到条件队列尾部
t.nextWaiter = node;
}
// 更新尾节点
lastWaiter = node;
return node;
} private void unlinkCancelledWaiters() {
// 读取条件队列头节点
Node t = firstWaiter;
Node trail = null;
while (t != null) {
// 读取后置节点
final Node next = t.nextWaiter;
// 当前节点的同步状态不是 Node.CONDITION,则需要踢除
if (t.waitStatus != Node.CONDITION) {
t.nextWaiter = null;
// 还没有有效的节点
if (trail == null) {
// 更新头节点
firstWaiter = next;
} else {
// 更新最近一个有效节点的 nextWaiter
trail.nextWaiter = next;
}
// 已经遍历到最后一个节点,并且该节点被取消
if (next == null) {
// 更新尾节点为最近一个有效节点
lastWaiter = trail;
}
} else {
// 写入最近一个有效节点
trail = t;
}
// 递归处理下一个节点
t = next;
}
} /**
* 释放互斥锁
*/
final int fullyRelease(Node node) {
try {
// 读取同步状态
final int savedState = getState();
// 在独占模式下释放锁
if (release(savedState)) {
return savedState;
}
throw new IllegalMonitorStateException();
} catch (final Throwable t) {
node.waitStatus = Node.CANCELLED;
throw t;
}
} /**
* 在独占模式下释放锁
*/
public final boolean release(int arg) {
// 尝试释放锁
if (tryRelease(arg)) {
// 读取头节点
final Node h = head;
// 头结点的同步状态不为 0,则唤醒其后置节点
if (h != null && h.waitStatus != 0) {
// 唤醒目标节点的后继节点
unparkSuccessor(h);
}
return true;
}
return false;
} /**
* 形参节点是否在条件队列中
*/
final boolean isOnSyncQueue(Node node) {
// 节点等待状态为 Node.CONDITION,或节点无前置节点,都说明其在同步队列中
if (node.waitStatus == Node.CONDITION || node.prev == null) {
return false;
}
// 节点的 next 不为 null,则其在条件队列中
if (node.next != null) {
return true;
}
// 从尾部开始查找节点
return findNodeFromTail(node);
} /**
* 从尾部向前查找形参节点,如果其在条件队列中,则返回 true
*/
private boolean findNodeFromTail(Node node) {
for (Node p = tail;;) {
// 当前节点就是目标节点
if (p == node) {
return true;
}
// 已经无前置节点
if (p == null) {
return false;
}
// 迭代前一个节点
p = p.prev;
}
} /**
* 检查线程中断状态
* 1)ConditionObject.THROW_IE 线程在被唤醒前,被其他线程中断
* 2)ConditionObject.REINTERRUPT 线程在被唤醒后,被其他线程中断
* 3)0 线程未被中断
*/
private int checkInterruptWhileWaiting(Node node) {
return Thread.interrupted() ?
transferAfterCancelledWait(node) ? ConditionObject.THROW_IE : ConditionObject.REINTERRUPT :
0;
} /**
* 等待条件已经取消,则尝试将当前节点转移到同步队列
*/
final boolean transferAfterCancelledWait(Node node) {
// 更新同步状态为 0
if (node.compareAndSetWaitStatus(Node.CONDITION, 0)) {
// 将当前节点加入到同步队列尾部,等待获取锁
enq(node);
return true;
}
/**
* 在节点被转移到同步队列时,线程被唤醒,则自旋等待其完全进入同步队列为止
*/
while (!isOnSyncQueue(node)) {
Thread.yield();
}
return false;
} /**
* 将节点加入到同步队列尾部
*/
private Node enq(Node node) {
for (;;) {
final Node oldTail = tail;
if (oldTail != null) {
node.setPrevRelaxed(oldTail);
if (compareAndSetTail(oldTail, node)) {
oldTail.next = node;
return oldTail;
}
} else {
initializeSyncQueue();
}
}
}
/**
* 在独占、线程不可中断的模式下获取锁,当前线程已经在同步队列中。
*/
final boolean acquireQueued(final Node node, int arg) {
boolean interrupted = false;
try {
for (;;) {
// 读取当前节点的前置节点
final Node p = node.predecessor();
// 如果前置节点是 head,则尝试获取锁
if (p == head && tryAcquire(arg)) {
// 获取成功,则设置当前节点为 head
setHead(node);
p.next = null; // help GC
// 返回线程中断标识
return interrupted;
}
// 当前线程是否需要被阻塞
if (AbstractQueuedSynchronizer.shouldParkAfterFailedAcquire(p, node)) {
// 阻塞当前线程,并等待唤醒,唤醒后返回其中断状态
interrupted |= parkAndCheckInterrupt();
}
}
// 线程运行过程中出现异常
} catch (final Throwable t) {
// 取消当前节点
cancelAcquire(node);
// 如果线程被设置中断标识
if (interrupted) {
// 则线程自我中断
AbstractQueuedSynchronizer.selfInterrupt();
}
throw t;
}
} private void reportInterruptAfterWait(int interruptMode)
throws InterruptedException {
// 1)线程在被唤醒前,被其他线程设置了中断标识
if (interruptMode == ConditionObject.THROW_IE) {
// 则抛出 InterruptedException 异常
throw new InterruptedException();
// 2)线程在被唤醒后,被其他线程设置了中断标识
} else if (interruptMode == ConditionObject.REINTERRUPT) {
// 中断当前线程
AbstractQueuedSynchronizer.selfInterrupt();
}
} private void enqueue(E e) {
// 读取底层对象数组
final Object[] items = this.items;
// 插入元素
items[putIndex] = e;
// 递增下一个插入索引,如果已经到达数组长度
if (++putIndex == items.length) {
// 循环更新到数组头部
putIndex = 0;
}
// 递增元素总数
count++;
// 唤醒在非空条件上阻塞等待的单个线程
notEmpty.signal();
} AbstractQueuedSynchronizer#
/**
* 唤醒在当前条件上阻塞等待的单个线程
*/
public final void signal() {
// 互斥锁没有被当前线程持有
if (!isHeldExclusively()) {
// 则抛出 IllegalMonitorStateException异常
throw new IllegalMonitorStateException();
}
// 读取条件队列中第一个等待的节点
final Node first = firstWaiter;
if (first != null) {
// 将该节点从条件队列转移到同步队列
doSignal(first);
}
} /**
* 将目标节点从条件队列转移到同步队列中
*/
private void doSignal(Node first) {
do {
// 更新 firstWaiter 为当前节点的后置节点
if ( (firstWaiter = first.nextWaiter) == null) {
// 如果无后置节点
lastWaiter = null;
}
first.nextWaiter = null;
// 转移成功则退出循环
} while (!transferForSignal(first) &&
(first = firstWaiter) != null);
} /**
* 将形参节点从条件队列转移到同步队列,转移成功返回 true
*/
final boolean transferForSignal(Node node) {
/**
* If cannot change waitStatus, the node has been cancelled.
* 节点在转移前被取消
*/
if (!node.compareAndSetWaitStatus(Node.CONDITION, 0)) {
return false;
} // 将节点加入到同步队列尾部
final Node p = enq(node);
// 读取其同步状态
final int ws = p.waitStatus;
/**
* 1)转移成功后线程被中断
* 2)原子更新状态失败,即节点被取消
*/
if (ws > 0 || !p.compareAndSetWaitStatus(ws, Node.SIGNAL)) {
// 唤醒驻留其上的线程
LockSupport.unpark(node.thread);
}
return true;
}
  • 读取元素
    @Override
public E take() throws InterruptedException {
final ReentrantLock lock = this.lock;
lock.lockInterruptibly();
try {
// 队列为空
while (count == 0) {
// 则在非空条件上阻塞等待
notEmpty.await();
}
return dequeue();
} finally {
lock.unlock();
}
} /**
* 移除并返回队列头部元素
*/
private E dequeue() {
final Object[] items = this.items;
final
// 读取目标元素
E e = (E) items[takeIndex];
// 将目标索引处的元素置空
items[takeIndex] = null;
// 循环递增读取索引
if (++takeIndex == items.length) {
takeIndex = 0;
}
count--;
if (itrs != null) {
itrs.elementDequeued();
}
// 唤醒在非满条件上等待的线程
notFull.signal();
return e;
}
  • 非阻塞添加元素
    /**
* 1)如果队列已满,则返回 false,元素添加失败。
* 2)如果队列有可用空间,则添加元素,并返回 true。
*/
@Override
public boolean offer(E e) {
Objects.requireNonNull(e);
final ReentrantLock lock = this.lock;
lock.lock();
try {
// 1)队列已满
if (count == items.length) {
return false;
// 2)队列中有可用位置
} else {
enqueue(e);
return true;
}
} finally {
lock.unlock();
}
} /**
* 1)如果队列有可用位置,则添加元素到队列尾部,并返回 true。
* 2)如果队列已满,在指定的超时时间内队列可用,则添加元素;否则,返回 false,添加失败。
*/
@Override
public boolean offer(E e, long timeout, TimeUnit unit)
throws InterruptedException { Objects.requireNonNull(e);
long nanos = unit.toNanos(timeout);
final ReentrantLock lock = this.lock;
lock.lockInterruptibly();
try {
while (count == items.length) {
if (nanos <= 0L) {
return false;
}
// 在非满条件上等待指定的超时时间
nanos = notFull.awaitNanos(nanos);
}
enqueue(e);
return true;
} finally {
lock.unlock();
}
} AbstractQueuedSynchronizer#awaitNanos
/**
* 1)当前线程被设置了中断标识,则抛出 InterruptedException 异常。
* 2)阻塞直到当前线程被唤醒、中断、或超时。
*/
public final long awaitNanos(long nanosTimeout)
throws InterruptedException {
// 如果线程已经被设置了中断标识,则抛出 InterruptedException 异常
if (Thread.interrupted()) {
throw new InterruptedException();
}
// 计算截止时间
final long deadline = System.nanoTime() + nanosTimeout;
final long initialNanos = nanosTimeout;
// 往条件队列中添加节点
final Node node = addConditionWaiter();
// 尝试释放互斥锁
final int savedState = fullyRelease(node);
int interruptMode = 0;
while (!isOnSyncQueue(node)) {
// 如果已经超时
if (nanosTimeout <= 0L) {
// 将节点从条件队列转移到同步队列中
transferAfterCancelledWait(node);
break;
}
// 如果超时时间 > 1000 纳秒
if (nanosTimeout > AbstractQueuedSynchronizer.SPIN_FOR_TIMEOUT_THRESHOLD) {
// 最多阻塞当期线程指定的超时时间
LockSupport.parkNanos(this, nanosTimeout);
}
if ((interruptMode = checkInterruptWhileWaiting(node)) != 0) {
break;
}
// 计算剩余时间
nanosTimeout = deadline - System.nanoTime();
}
// 尝试获取互斥锁
if (acquireQueued(node, savedState) && interruptMode != ConditionObject.THROW_IE) {
interruptMode = ConditionObject.REINTERRUPT;
}
if (node.nextWaiter != null) {
unlinkCancelledWaiters();
}
if (interruptMode != 0) {
reportInterruptAfterWait(interruptMode);
}
// 计算剩余时间
final long remaining = deadline - System.nanoTime(); // avoid overflow
return remaining <= initialNanos ? remaining : Long.MIN_VALUE;
}
  • 非阻塞读取元素
    /**
* 1)如果队列为空,则返回 null
* 2)如果队列非空,则移除并返回队列头部元素
* created by ZXD at 2 Dec 2018 T 09:41:30
* @return
*/
@Override
public E poll() {
final ReentrantLock lock = this.lock;
lock.lock();
try {
return count == 0 ? null : dequeue();
} finally {
lock.unlock();
}
} /**
* 1)如果队列非空,则移除并返回头部元素。
* 2)如果队列为空,则最多在指定的超时时间内等待队列有元素可用,
* 如果有则移除并返回队列头部;如果已经超时,则返回 null.
*/
@Override
public E poll(long timeout, TimeUnit unit) throws InterruptedException {
// 计算以纳秒为单位的超时时间
long nanos = unit.toNanos(timeout);
final ReentrantLock lock = this.lock;
lock.lockInterruptibly();
try {
while (count == 0) {
if (nanos <= 0L) {
return null;
}
// 在非空条件上最多阻塞 nanos 纳秒
nanos = notEmpty.awaitNanos(nanos);
}
return dequeue();
} finally {
lock.unlock();
}
}

ArrayBlockingQueue 源码分析的更多相关文章

  1. java多线程系列(九)---ArrayBlockingQueue源码分析

    java多线程系列(九)---ArrayBlockingQueue源码分析 目录 认识cpu.核心与线程 java多线程系列(一)之java多线程技能 java多线程系列(二)之对象变量的并发访问 j ...

  2. 死磕 java集合之ArrayBlockingQueue源码分析

    问题 (1)ArrayBlockingQueue的实现方式? (2)ArrayBlockingQueue是否需要扩容? (3)ArrayBlockingQueue有什么缺点? 简介 ArrayBloc ...

  3. 并发编程(八)—— Java 并发队列 BlockingQueue 实现之 ArrayBlockingQueue 源码分析

    开篇先介绍下 BlockingQueue 这个接口的规则,后面再看其实现. 阻塞队列概要 阻塞队列与我们平常接触的普通队列(LinkedList或ArrayList等)的最大不同点,在于阻塞队列的阻塞 ...

  4. Java并发编程笔记之ArrayBlockingQueue源码分析

    JDK 中基于数组的阻塞队列 ArrayBlockingQueue 原理剖析,ArrayBlockingQueue 内部如何基于一把独占锁以及对应的两个条件变量实现出入队操作的线程安全? 首先我们先大 ...

  5. 多线程高并发编程(12) -- 阻塞算法实现ArrayBlockingQueue源码分析(1)

    一.前言 前文探究了非阻塞算法的实现ConcurrentLinkedQueue安全队列,也说明了阻塞算法实现的两种方式,使用一把锁(出队和入队同一把锁ArrayBlockingQueue)和两把锁(出 ...

  6. Java核心复习——J.U.C ArrayBlockingQueue源码分析

    介绍 依赖关系 源码 构造方法 public ArrayBlockingQueue(int capacity) { this(capacity, false);//默认构造非公平的有界队列 } pub ...

  7. ArrayBlockingQueue源码分析

    ArrayBlockingQueue是一个基于数组实现的有界的阻塞队列. 属性 //底层存储元素的数组.为final说明一旦初始化,容量不可变,所以是有界的. final Object[] items ...

  8. 【JUC】JDK1.8源码分析之ArrayBlockingQueue(三)

    一.前言 在完成Map下的并发集合后,现在来分析ArrayBlockingQueue,ArrayBlockingQueue可以用作一个阻塞型队列,支持多任务并发操作,有了之前看源码的积累,再看Arra ...

  9. JDK源码分析—— ArrayBlockingQueue 和 LinkedBlockingQueue

    JDK源码分析—— ArrayBlockingQueue 和 LinkedBlockingQueue 目的:本文通过分析JDK源码来对比ArrayBlockingQueue 和LinkedBlocki ...

随机推荐

  1. 二、JVM — 垃圾回收

    JVM 垃圾回收 写在前面 本节常见面试题 本文导火索 1 揭开 JVM 内存分配与回收的神秘面纱 1.1 对象优先在 eden 区分配 1.2 大对象直接进入老年代 1.3 长期存活的对象将进入老年 ...

  2. [LOJ 6253] Yazid 的新生舞会

    link $solution:$ 不知道为什么别人的代码能写的非常短,难道就是写差分的好处? 这种题肯定是算每个众数的贡献,考虑通过暴力众数求出个数. 现在考虑众数 $x$ ,则在序列 $a$ 中将等 ...

  3. 自动清理ES索引脚本

    #/bin/bash #指定日期(3个月前) DATA=`date -d "3 month ago" +%Y.%m.%d` #当前日期 time=`date` #删除3个月前的日志 ...

  4. php strpos() 函数介绍与使用方法详解

    本文主要和大家介绍PHP中mb_strpos的使用技巧,通过使用语法以及实例给大家详细分析了用法,需要的朋友参考学习下.希望能帮助到大家.mb_strpos(PHP 4 >= 4.0.6, PH ...

  5. Java中实现多态的条件是什么

    java中实现多态需要三个条件: 1,需要有继承关系的存在. 2,需要有方法的重写. 3,需要有父类的引用指向子类对象.

  6. 出去就餐并且理解Express.js的基本知识

    Going out to eat and understanding the basics of Express.js出去就餐并且理解Express.js的基本知识 原文:Going out to e ...

  7. react 错误处理

    https://www.jianshu.com/p/61d09e488743 https://codepen.io/sgroff04/pen/dVbgJy/

  8. Linux下配置JDK环境

    安装前需要查询Linux中是否已经存在jdk 如果存在,将存在的jdk删除 在/etc/profile中添加以下 JAVA_HOME为jdk的安装目录 PATH为jdk可执行文件的目录 使用sourc ...

  9. Apache 网站日志分析

    1.获得访问前 10 位的 ip 地址 [root@apache ~]# cat access_log |awk '{print $1}'|sort|uniq -c|sort -nr|head -10 ...

  10. docker持久化之存储卷

         启动时敲:   docker run -it -v /var/mydata:/abc myos   #前面写真实机里的文件夹,冒号后面写docker里面的文件夹,最后写镜像名 (可以同时挂载 ...