LinkedBlockingQueue 源码分析
LinkedBlockingQueue
LinkedBlockingQueue 是基于链表实现的,可以选择有界或无界的阻塞队列。
队列的元素按照 FIFO 的顺序访问,新增元素添加到队列尾部,移除元素从队列头部开始。
队列头部通过 takeLock 进行并发控制,队列尾部通过 putLock 进行并发控制,
该队列最多可以有两个线程同时操作,其吞吐量要高于 ArrayBlockQueue。
创建实例
/**
* 单向链表节点
*/
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; }
}
/** 阻塞队列的容量,如果不指定,则为 Integer.MAX_VALUE */
private final int capacity;
/** 当前的元素总数 */
private final AtomicInteger count = new AtomicInteger();
/**
* 链表头结点
* Invariant: head.item == null
*/
transient Node<E> head;
/**
* 链表尾节点
* Invariant: last.next == null
*/
private transient Node<E> last;
/** 执行读取操作时必须持有的锁 */
private final ReentrantLock takeLock = new ReentrantLock();
/** 队列为空时执行的 take 操作,当前线程将在此条件阻塞等待 */
private final Condition notEmpty = takeLock.newCondition();
/** 执行写入操作时必须持有的锁 */
private final ReentrantLock putLock = new ReentrantLock();
/** 队列已满时执行的 put 操作,当前线程将在此条件阻塞等待 */
private final Condition notFull = putLock.newCondition();
/**
* 创建一个容量为 Integer.MAX_VALUE 的 LinkedBlockingQueue 实例
*/
public LinkedBlockingQueue() {
this(Integer.MAX_VALUE);
}
/**
* 创建一个容量为 capacity 的 LinkedBlockingQueue 实例
*/
public LinkedBlockingQueue(int capacity) {
if (capacity <= 0) {
throw new IllegalArgumentException();
}
this.capacity = capacity;
last = head = new Node<>(null);
}
写入元素
- 在队列尾部插入元素,如果队列已满,则阻塞等待有可用空间之后,再尝试插入
/**
* 在队列尾部插入元素,如果队列已满,则阻塞等待有可用空间之后,再尝试插入。
*/
@Override
public void put(E e) throws InterruptedException {
if (e == null) {
throw new NullPointerException();
}
// Note: convention in all put/take/etc is to preset local var
// holding count negative to indicate failure unless set.
int c = -1;
// 创建新节点
final Node<E> node = new Node<>(e);
// 读取写锁
final ReentrantLock putLock = this.putLock;
// 读取元素计数值
final AtomicInteger count = this.count;
// 可中断地锁定
putLock.lockInterruptibly();
try {
// 如果当前队列已满
while (count.get() == capacity) {
// 则在非满条件上阻塞等待,线程被唤醒后进行重试
notFull.await();
}
// 加入队列尾部
enqueue(node);
// 递增元素总数
c = count.getAndIncrement();
// 如果添加元素之后还有可用空间
if (c + 1 < capacity) {
// 唤醒在非满条件上阻塞等待的线程
notFull.signal();
}
} finally {
// 释放写锁
putLock.unlock();
}
// 如果是添加的第一个元素
if (c == 0) {
// 唤醒在非空条件上阻塞等待的线程来读取元素
signalNotEmpty();
}
}
/**
* 将节点插入队列尾部
*/
private void enqueue(Node<E> node) {
last = last.next = node;
}
/**
* Signals a waiting take. Called only from put/offer
* 唤醒一个等待读取元素的线程
*/
private void signalNotEmpty() {
final ReentrantLock takeLock = this.takeLock;
takeLock.lock();
try {
notEmpty.signal();
} finally {
takeLock.unlock();
}
}
- 如果队列有可用空间,则将元素添加到队列尾部,并返回 true;如果队列已满,则立即返回 false,元素被丢弃。
/**
* 如果队列有可用空间,则将元素添加到队列尾部,并返回 true;
* 如果队列已满,则立即返回 false,元素被丢弃。
*/
@Override
public boolean offer(E e) {
if (e == null) {
throw new NullPointerException();
}
final AtomicInteger count = this.count;
// 队列已满
if (count.get() == capacity) {
// 返回 false
return false;
}
int c = -1;
// 新建节点
final Node<E> node = new Node<>(e);
// 读取写锁
final ReentrantLock putLock = this.putLock;
// 获取锁
putLock.lock();
try {
// 进行二次判断
if (count.get() < capacity) {
// 将节点加入到队列尾部
enqueue(node);
// 递增元素总个数
c = count.getAndIncrement();
if (c + 1 < capacity) {
notFull.signal();
}
}
} finally {
putLock.unlock();
}
if (c == 0) {
signalNotEmpty();
}
return c >= 0;
}
- 在指定的超时时间内,尝试将元素插入到队列尾部,插入成功返回 true
/**
* 在指定的超时时间内,尝试将元素插入到队列尾部,插入成功返回 true
*/
@Override
public boolean offer(E e, long timeout, TimeUnit unit)
throws InterruptedException {
if (e == null) {
throw new NullPointerException();
}
// 转换为纳秒
long nanos = unit.toNanos(timeout);
int c = -1;
final ReentrantLock putLock = this.putLock;
final AtomicInteger count = this.count;
putLock.lockInterruptibly();
try {
// 队列已满
while (count.get() == capacity) {
// 已经超时则直接返回 false
if (nanos <= 0L) {
return false;
}
// 最多阻塞等待指定的超时时间后再次尝试添加元素
nanos = notFull.awaitNanos(nanos);
}
enqueue(new Node<>(e));
c = count.getAndIncrement();
if (c + 1 < capacity) {
notFull.signal();
}
} finally {
putLock.unlock();
}
if (c == 0) {
signalNotEmpty();
}
return true;
}
读取元素
- 如果队列为空,则阻塞等待有可用元素,否则移除并获取队列头部元素
/**
* 如果队列为空,则阻塞等待有可用元素,否则移除并获取队列头部元素
* created by ZXD at 6 Dec 2018 T 20:20:15
* @return
* @throws InterruptedException
*/
@Override
public E take() throws InterruptedException {
E x;
int c = -1;
final AtomicInteger count = this.count;
final ReentrantLock takeLock = this.takeLock;
takeLock.lockInterruptibly();
try {
// 队列为空
while (count.get() == 0) {
// 在非空条件上阻塞等待,线程被唤醒后再次尝试读取
notEmpty.await();
}
// 移除队列头部元素
x = dequeue();
// 递减总数
c = count.getAndDecrement();
// 如果还有元素可用
if (c > 1) {
// 唤醒在非空条件上阻塞等待的线程
notEmpty.signal();
}
} finally {
takeLock.unlock();
}
// 如果有可用空间
if (c == capacity) {
// 唤醒在非满条件上阻塞等待的线程来插入元素
signalNotFull();
}
return x;
}
/**
* 移除并返回头部节点元素
*/
private E dequeue() {
// 读取头节点
final Node<E> h = head;
// 读取后继节点
final Node<E> first = h.next;
// 清除旧头结点
h.next = h; // help GC
// 写入新头节点
head = first;
// 读取元素值
final E x = first.item;
// 清除头结点的元素值,它只作为一个标记节点
first.item = null;
// 返回元素值
return x;
}
- 如果队列为空,则直接返回 null,否则尝试移除并返回头部元素
/**
* 如果队列为空,则直接返回 null,否则尝试移除并返回头部元素
* created by ZXD at 6 Dec 2018 T 20:23:51
* @return
*/
@Override
public E poll() {
final AtomicInteger count = this.count;
// 队列为空时直接返回 null
if (count.get() == 0) {
return null;
}
E x = null;
int c = -1;
final ReentrantLock takeLock = this.takeLock;
takeLock.lock();
try {
// 有元素可用
if (count.get() > 0) {
// 移除并返回头部元素
x = dequeue();
c = count.getAndDecrement();
if (c > 1) {
notEmpty.signal();
}
}
} finally {
takeLock.unlock();
}
if (c == capacity) {
signalNotFull();
}
return x;
}
- 尝试在指定的超时时间内移除并返回头部元素,如果已经超时,则返回 null
/**
* 尝试在指定的超时时间内移除并返回头部元素,如果已经超时,则返回 null
* created by ZXD at 6 Dec 2018 T 20:27:27
* @param timeout
* @param unit
* @return
* @throws InterruptedException
*/
@Override
public E poll(long timeout, TimeUnit unit) throws InterruptedException {
E x = null;
int c = -1;
long nanos = unit.toNanos(timeout);
final AtomicInteger count = this.count;
final ReentrantLock takeLock = this.takeLock;
takeLock.lockInterruptibly();
try {
// 队列为空
while (count.get() == 0) {
// 已经超时则直接返回 null
if (nanos <= 0L) {
return null;
}
// 在非空条件上阻塞等待指定的纳秒数,被唤醒后再次进行读取
nanos = notEmpty.awaitNanos(nanos);
}
x = dequeue();
c = count.getAndDecrement();
if (c > 1) {
notEmpty.signal();
}
} finally {
takeLock.unlock();
}
if (c == capacity) {
signalNotFull();
}
return x;
}
LinkedBlockingQueue 源码分析的更多相关文章
- LinkedBlockingQueue源码分析
1. LinkedBlockingQueue源码分析(JDK8) 2. LinkedBlockingQueue源码分析 啦啦啦
- 死磕 java集合之LinkedBlockingQueue源码分析
问题 (1)LinkedBlockingQueue的实现方式? (2)LinkedBlockingQueue是有界的还是无界的队列? (3)LinkedBlockingQueue相比ArrayBloc ...
- 并发编程(九)—— Java 并发队列 BlockingQueue 实现之 LinkedBlockingQueue 源码分析
LinkedBlockingQueue 在看源码之前,通过查询API发现对LinkedBlockingQueue特点的简单介绍: 1.LinkedBlockingQueue是一个由链表实现的有界队列阻 ...
- java并发编程基础-ReentrantLock及LinkedBlockingQueue源码分析
ReentrantLock是一个较为常用的锁对象.在上次分析的uil开源项目中也多次被用到,下面谈谈其概念和基本使用. 概念 一个可重入的互斥锁定 Lock,它具有与使用 synchronized 相 ...
- 并发队列ConcurrentLinkedQueue与LinkedBlockingQueue源码分析与对比
目录 前言 ConcurrentLinkedQueue 使用方法 存储结构 初始化 入队 出队 获取容器元素数量 LinkedBlockingQueue 使用方法 存储结构 初始化 入队 出队 获取容 ...
- Java核心复习——J.U.C LinkedBlockingQueue源码分析
参考文档 LinkedBlockingQueue和ArrayBlockingQueue的异同
- 【JUC】JDK1.8源码分析之LinkedBlockingQueue(四)
一.前言 分析完了ArrayBlockingQueue后,接着分析LinkedBlockingQueue,与ArrayBlockingQueue不相同,LinkedBlockingQueue底层采用的 ...
- JDK源码分析—— ArrayBlockingQueue 和 LinkedBlockingQueue
JDK源码分析—— ArrayBlockingQueue 和 LinkedBlockingQueue 目的:本文通过分析JDK源码来对比ArrayBlockingQueue 和LinkedBlocki ...
- JDK源码分析(11)之 BlockingQueue 相关
本文将主要结合源码对 JDK 中的阻塞队列进行分析,并比较其各自的特点: 一.BlockingQueue 概述 说到阻塞队列想到的第一个应用场景可能就是生产者消费者模式了,如图所示: 根据上图所示,明 ...
随机推荐
- bootstrap中的横的列
col-md-6都是可以嵌套的,所以12列都是虚拟的 所以bootstrap是怎么完成的?都是通过绝对的像素值吗?还是自动计算出了本区域的像素数,然后设置的? 看样子应该是后者,所以整个bootstr ...
- 一分钟搭建 Web 版的 PHP 代码调试利器
一.背景 俗话说:"工欲善其事,必先利其器".作为一门程序员,我们在工作中,经常需要调试某一片段的代码,但是又不想打开繁重的 IDE (代码编辑器).使用在线工具调试代码有时有 ...
- P2510 [HAOI2008]下落的圆盘
传送门 首先考虑两个圆覆盖的情况,我们可以求出圆心与交点连线 $A$ 的极角 具体就是求出两圆心连线 $B$ 极角加上余弦定理加反余弦求出 $A,B$ 之间夹角 ,然后覆盖了多少就可以得出 但是多个圆 ...
- pip和pip3的区别
安装了python3之后,会有pip3 1. 使用pip install XXX 新安装的库会放在这个目录下面 python2.7/site-packages 2. 使用pip3 install XX ...
- numpy.random.randn()和numpy.random.rand()
1 numpy.random.rand() (1)numpy.random.rand(d0,d1,…,dn) rand函数根据给定维度生成[0,1)之间的数据,包含0,不包含1 dn表格每个维度 返回 ...
- jQuery进阶第二天(2019 10.10)
一.事件流程 1.事件的三要素: 事件源:发生事件的对象 事件类型:类型比如单击.双击.鼠标的移入.移除 事件处理程序: 触发事件之后做些什么,事件处理的函数 <body> <but ...
- GUI学习之十九——QFontComboBox学习总结
我们上一章学习了QComboBox的用法,这一章我们来看一下它的一个比较常用的子类:QFontComboBox(). 一.描述: QFontComboBox()是QComboBox()的一个子类,但是 ...
- Vue this.$nextTick原理
虽然 Vue.js 通常鼓励开发人员沿着“数据驱动”的方式思考,避免直接接触 DOM,但是有时我们确实要这么做.比如一个新闻滚动的列表项.如果在这里需要操作dom, 应该是等待 Vue 完成更新 DO ...
- rsync服务实践
RSYNC数据备份 RSYNC=Remote Sync 远程同步 高效,一定要结合shell 官方网站:https://rsync.samba.org/ Author: Andrew Tr ...
- SpringBoot实体类对象和json格式的转化
1.引入maven依赖 <dependency> <groupId>com.alibaba</groupId> <artifactId>fastjson ...