问题

(1)LinkedBlockingQueue的实现方式?

(2)LinkedBlockingQueue是有界的还是无界的队列?

(3)LinkedBlockingQueue相比ArrayBlockingQueue有什么改进?

简介

LinkedBlockingQueue是java并发包下一个以单链表实现的阻塞队列,它是线程安全的,至于它是不是有界的,请看下面的分析。

源码分析

主要属性

// 容量
private final int capacity; // 元素数量
private final AtomicInteger count = new AtomicInteger(); // 链表头
transient Node<E> head; // 链表尾
private transient Node<E> last; // take锁
private final ReentrantLock takeLock = new ReentrantLock(); // notEmpty条件
// 当队列无元素时,take锁会阻塞在notEmpty条件上,等待其它线程唤醒
private final Condition notEmpty = takeLock.newCondition(); // 放锁
private final ReentrantLock putLock = new ReentrantLock(); // notFull条件
// 当队列满了时,put锁会会阻塞在notFull上,等待其它线程唤醒
private final Condition notFull = putLock.newCondition();

(1)capacity,有容量,可以理解为LinkedBlockingQueue是有界队列

(2)head, last,链表头、链表尾指针

(3)takeLock,notEmpty,take锁及其对应的条件

(4)putLock, notFull,put锁及其对应的条件

(5)入队、出队使用两个不同的锁控制,锁分离,提高效率

内部类

static class Node<E> {
E item; Node<E> next; Node(E x) { item = x; }
}

典型的单链表结构。

主要构造方法

public LinkedBlockingQueue() {
// 如果没传容量,就使用最大int值初始化其容量
this(Integer.MAX_VALUE);
} public LinkedBlockingQueue(int capacity) {
if (capacity <= 0) throw new IllegalArgumentException();
this.capacity = capacity;
// 初始化head和last指针为空值节点
last = head = new Node<E>(null);
}

入队

入队同样有四个方法,我们这里只分析最重要的一个,put(E e)方法:

public void put(E e) throws InterruptedException {
// 不允许null元素
if (e == null) throw new NullPointerException();
int c = -1;
// 新建一个节点
Node<E> node = new Node<E>(e);
final ReentrantLock putLock = this.putLock;
final AtomicInteger count = this.count;
// 使用put锁加锁
putLock.lockInterruptibly();
try {
// 如果队列满了,就阻塞在notFull条件上
// 等待被其它线程唤醒
while (count.get() == capacity) {
notFull.await();
}
// 队列不满了,就入队
enqueue(node);
// 队列长度加1
c = count.getAndIncrement();
// 如果现队列长度如果小于容量
// 就再唤醒一个阻塞在notFull条件上的线程
// 这里为啥要唤醒一下呢?
// 因为可能有很多线程阻塞在notFull这个条件上的
// 而取元素时只有取之前队列是满的才会唤醒notFull
// 为什么队列满的才唤醒notFull呢?
// 因为唤醒是需要加putLock的,这是为了减少锁的次数
// 所以,这里索性在放完元素就检测一下,未满就唤醒其它notFull上的线程
// 说白了,这也是锁分离带来的代价
if (c + 1 < capacity)
notFull.signal();
} finally {
// 释放锁
putLock.unlock();
}
// 如果原队列长度为0,现在加了一个元素后立即唤醒notEmpty条件
if (c == 0)
signalNotEmpty();
} private void enqueue(Node<E> node) {
// 直接加到last后面
last = last.next = node;
} private void signalNotEmpty() {
final ReentrantLock takeLock = this.takeLock;
// 加take锁
takeLock.lock();
try {
// 唤醒notEmpty条件
notEmpty.signal();
} finally {
// 解锁
takeLock.unlock();
}
}

(1)使用putLock加锁;

(2)如果队列满了就阻塞在notFull条件上;

(3)否则就入队;

(4)如果入队后元素数量小于容量,唤醒其它阻塞在notFull条件上的线程;

(5)释放锁;

(6)如果放元素之前队列长度为0,就唤醒notEmpty条件;

出队

出队同样也有四个方法,我们这里只分析最重要的那一个,take()方法:

public E take() throws InterruptedException {
E x;
int c = -1;
final AtomicInteger count = this.count;
final ReentrantLock takeLock = this.takeLock;
// 使用takeLock加锁
takeLock.lockInterruptibly();
try {
// 如果队列无元素,则阻塞在notEmpty条件上
while (count.get() == 0) {
notEmpty.await();
}
// 否则,出队
x = dequeue();
// 获取出队前队列的长度
c = count.getAndDecrement();
// 如果取之前队列长度大于1,则唤醒notEmpty
if (c > 1)
notEmpty.signal();
} finally {
// 释放锁
takeLock.unlock();
}
// 如果取之前队列长度等于容量
// 则唤醒notFull
if (c == capacity)
signalNotFull();
return x;
} private E dequeue() {
// head节点本身是不存储任何元素的
// 这里把head删除,并把head下一个节点作为新的值
// 并把其值置空,返回原来的值
Node<E> h = head;
Node<E> first = h.next;
h.next = h; // help GC
head = first;
E x = first.item;
first.item = null;
return x;
} private void signalNotFull() {
final ReentrantLock putLock = this.putLock;
putLock.lock();
try {
// 唤醒notFull
notFull.signal();
} finally {
putLock.unlock();
}
}

(1)使用takeLock加锁;

(2)如果队列空了就阻塞在notEmpty条件上;

(3)否则就出队;

(4)如果出队前元素数量大于1,唤醒其它阻塞在notEmpty条件上的线程;

(5)释放锁;

(6)如果取元素之前队列长度等于容量,就唤醒notFull条件;

总结

(1)LinkedBlockingQueue采用单链表的形式实现;

(2)LinkedBlockingQueue采用两把锁的锁分离技术实现入队出队互不阻塞;

(3)LinkedBlockingQueue是有界队列,不传入容量时默认为最大int值;

彩蛋

(1)LinkedBlockingQueue与ArrayBlockingQueue对比?

a)后者入队出队采用一把锁,导致入队出队相互阻塞,效率低下;

b)前才入队出队采用两把锁,入队出队互不干扰,效率较高;

c)二者都是有界队列,如果长度相等且出队速度跟不上入队速度,都会导致大量线程阻塞;

d)前者如果初始化不传入初始容量,则使用最大int值,如果出队速度跟不上入队速度,会导致队列特别长,占用大量内存;


欢迎关注我的公众号“彤哥读源码”,查看更多源码系列文章, 与彤哥一起畅游源码的海洋。

死磕 java集合之LinkedBlockingQueue源码分析的更多相关文章

  1. 死磕 java集合之DelayQueue源码分析

    问题 (1)DelayQueue是阻塞队列吗? (2)DelayQueue的实现方式? (3)DelayQueue主要用于什么场景? 简介 DelayQueue是java并发包下的延时阻塞队列,常用于 ...

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

    问题 (1)PriorityBlockingQueue的实现方式? (2)PriorityBlockingQueue是否需要扩容? (3)PriorityBlockingQueue是怎么控制并发安全的 ...

  3. 死磕 java集合之PriorityQueue源码分析

    问题 (1)什么是优先级队列? (2)怎么实现一个优先级队列? (3)PriorityQueue是线程安全的吗? (4)PriorityQueue就有序的吗? 简介 优先级队列,是0个或多个元素的集合 ...

  4. 死磕 java集合之CopyOnWriteArraySet源码分析——内含巧妙设计

    问题 (1)CopyOnWriteArraySet是用Map实现的吗? (2)CopyOnWriteArraySet是有序的吗? (3)CopyOnWriteArraySet是并发安全的吗? (4)C ...

  5. 死磕 java集合之LinkedHashSet源码分析

    问题 (1)LinkedHashSet的底层使用什么存储元素? (2)LinkedHashSet与HashSet有什么不同? (3)LinkedHashSet是有序的吗? (4)LinkedHashS ...

  6. 死磕 java集合之ConcurrentHashMap源码分析(三)

    本章接着上两章,链接直达: 死磕 java集合之ConcurrentHashMap源码分析(一) 死磕 java集合之ConcurrentHashMap源码分析(二) 删除元素 删除元素跟添加元素一样 ...

  7. 死磕 java集合之ArrayDeque源码分析

    问题 (1)什么是双端队列? (2)ArrayDeque是怎么实现双端队列的? (3)ArrayDeque是线程安全的吗? (4)ArrayDeque是有界的吗? 简介 双端队列是一种特殊的队列,它的 ...

  8. 【死磕 Java 集合】— ConcurrentSkipListMap源码分析

    转自:http://cmsblogs.com/?p=4773 [隐藏目录] 前情提要 简介 存储结构 源码分析 主要内部类 构造方法 添加元素 添加元素举例 删除元素 删除元素举例 查找元素 查找元素 ...

  9. 死磕 java集合之LinkedList源码分析

    问题 (1)LinkedList只是一个List吗? (2)LinkedList还有其它什么特性吗? (3)LinkedList为啥经常拿出来跟ArrayList比较? (4)我为什么把LinkedL ...

随机推荐

  1. kindeditor修改允许上传的图片、视频、音频大小

    在jsp文件夹下,有个upload_json.jsp文件,打开找到: //最大文件大小 ; 修改数值即可.默认1000000,即为1M.

  2. Python学习摘要201802

    [基础]变量设计机制 [个人理解]python的变量与C++语言中的指针类似,是指向内存数据的一个引用.变量分为不可变变量string/int/float/tuple和可变变量list/dict. 对 ...

  3. java实现堆结构

    一.前言 之前用java实现堆结构,一直用的优先队列,但是在实际的面试中,可能会要求用数组实现,所以还是用java老老实实的实现一遍堆结构吧. 二.概念 堆,有两种形式,一种是大根堆,另一种是小根堆. ...

  4. 微信小程序入门一

    基本的准备工作 -知识储备 --基础:HTML+JS+CSS --进阶:React.Vue -工具安装 --工具由微信官方提供 ---下载地址:https://github.com/zce/weapp ...

  5. Python测试远程端口连接时间

    问题 最近自己服务器访问别人的服务器,有时候会报超时错误,有时候又能够正常访问别人服务器. 思路 最开始猜测是网络不稳定造成的,但是自己没有收集什么时候超时,什么时候能正常访问别人服务器的日志,搞网络 ...

  6. 前端工程化(二)---webpack配置

    导航 前端工程化(一)---工程基础目录搭建 前端工程化(二)---webpack配置 前端工程化(三)---Vue的开发模式 前端工程化(四)---helloWord 继续上一遍的配置,本节主要记录 ...

  7. 【转】javascript 浮点数运算问题

    大多数语言在处理浮点数的时候都会遇到精度问题,但是在JS里似乎特别严重,来看一个例子 alert(45.6*13); 结果居然是592.800000000001,当然加法之类的也会有这个问题 那这是j ...

  8. MySQL事务的的介绍及使用

    事务的特性 1.原子性(Atomicity):原子性是指事务是一个不可分割的工作单位,事务中的操作要么都发生,要么都不发生. 2.一致性(Consistency):在一个事务中,事务前后数据的完整性必 ...

  9. 几张图帮你理解 docker 基本原理及快速入门

    写的非常好的一篇文章,不知道为什么被删除了.  利用Google快照,做个存档. 快照地址:地址 作者地址:青牛 什么是docker Docker 是一个开源项目,诞生于 2013 年初,最初是 do ...

  10. C++/C实现各种排序算法(持续更新)--冒泡排序,选择排序,归并排序

    2018 3 17 今日总结一下C++中的排序算法: 1冒泡排序 它重复地走访过要排序的数列,一次比较两个元素,如果他们的顺序错误就把他们交换过来.走访数列的工作是重复地进行直到没有再需要交换,也就是 ...