一、LinkedBlockingQueue简介

  LinkedBlockingQueue是一个使用链表完成队列操作的阻塞队列。链表是单向链表,而不是双向链表。采用对于的next构成链表的方式来存储对象。由于读只操作队头,而写只操作队尾,这里巧妙地采用了两把锁,对put和offer采用putLock,对take和poll采用takeLock,即为写锁和读锁,这两个锁实现阻塞(“two lock queue” algorithm)。避免了读写时相互竞争锁的现象,因为其对于生产者端和消费者端分别采用了独立的锁来控制数据同步,因此LinkedBlockingQueue在高并发读写操作都多的情况下,性能会比ArrayBlockingQueue好很多,在遍历以及删除元素时则要把两把锁都锁住。

  可选的容量范围构造方法参数作为防止队列过度扩展的一种方法。如果未指定容量,则它等于 Integer.MAX_VALUE。除非插入节点会使队列超出容量,否则每次插入后会动态地创建链接节点。

注意1:容量范围可以在构造方法参数中指定作为防止队列过度扩展。如果未指定容量,则它等于 Integer.MAX_VALUE 
 注意2:它是线程安全的,是线程阻塞的。 
 注意3:不接受 null 元素 
 注意4:它实现了BlockingQueue接口。关于BlockingQueue,请参考《BlockingQueue》 
 注意5:它没有线程ArrayBlockingQueue那样的公平性设置。为什么这样设计呢?puzzle. 
 注意6:此类及其迭代器实现了 Collection 和 Iterator 接口的所有可选 方法

二、LinkedBlockingQueue源码分析

2.1、LinkedBlockingQueue的lock

  在LinkedBlockingQueue中有2个lock,分别是读锁和写锁,读写的的锁都是全局的,而且是final的。

ArrayBlockingQueue只有1个锁,添加数据和删除数据的时候只能有1个被执行,不允许并行执行。而LinkedBlockingQueue有2个锁,写锁和读锁,添加数据和删除数据是可以并行进行的,当然添加数据和删除数据的时候只能有1个线程各自执行。

    /** Lock held by take, poll, etc */
private final ReentrantLock takeLock = new ReentrantLock(); /** Wait queue for waiting takes */
private final Condition notEmpty = takeLock.newCondition(); /** Lock held by put, offer, etc */
private final ReentrantLock putLock = new ReentrantLock(); /** Wait queue for waiting puts */
private final Condition notFull = putLock.newCondition();

LinkedBlockingQueue使用ReentrantLock来实现的添加元素原子操作,具体可以看poll和offer中的阻塞awaitNanos(nanos)是使用了Condition中的AQS中的一个方法。

2.2、成员变量

    /** The capacity bound, or Integer.MAX_VALUE if none */
private final int capacity; /** Current number of elements */
private final AtomicInteger count = new AtomicInteger(); /**
* 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;

如图LinkedBlockingQueue中也有两个Node分别用来存放首尾节点,并且里面有个初始值为0的原子变量count用来记录队列元素个数。

2.3、入队

  inkedBlockingQueue有不同的几个数据添加方法,add、offer、put方法,add方法内部调用offer方法。

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;
Node<E> node = new Node(e); // 容量没满,以新元素构造节点
final ReentrantLock putLock = this.putLock;
putLock.lock(); // 放锁加锁,保证调用offer方法的时候只有1个线程
try {
if (count.get() < capacity) { // 再次判断容量是否已满,因为可能拿锁在进行消费数据,没满的话继续执行
enqueue(node); // 节点添加到链表尾部
c = count.getAndIncrement(); // 元素个数+1
if (c + 1 < capacity) // 如果容量还没满
notFull.signal(); // 在放锁的条件对象notFull上唤醒正在等待的线程,表示可以再次往队列里面加数据了,队列还没满
}
} finally {
putLock.unlock(); // 释放放锁,让其他线程可以调用offer方法
}
if (c == 0) // 由于存在放锁和拿锁,这里可能拿锁一直在消费数据,count会变化。这里的if条件表示如果队列中还有1条数据
signalNotEmpty(); // 在拿锁的条件对象notEmpty上唤醒正在等待的1个线程,表示队列里还有1条数据,可以进行消费
return c >= 0; // 添加成功返回true,否则返回false
}

put方法:

public void put(E e) throws InterruptedException {
if (e == null) throw new NullPointerException(); // 不允许空元素
int c = -1;
Node<E> node = new Node(e); // 以新元素构造节点
final ReentrantLock putLock = this.putLock;
final AtomicInteger count = this.count;
putLock.lockInterruptibly(); // 放锁加锁,保证调用put方法的时候只有1个线程
try {
while (count.get() == capacity) { // 如果容量满了
notFull.await(); // 阻塞并挂起当前线程
}
enqueue(node); // 节点添加到链表尾部
c = count.getAndIncrement(); // 元素个数+1
if (c + 1 < capacity) // 如果容量还没满
notFull.signal(); // 在放锁的条件对象notFull上唤醒正在等待的线程,表示可以再次往队列里面加数据了,队列还没满
} finally {
putLock.unlock(); // 释放放锁,让其他线程可以调用put方法
}
if (c == 0) // 由于存在放锁和拿锁,这里可能拿锁一直在消费数据,count会变化。这里的if条件表示如果队列中还有1条数据
signalNotEmpty(); // 在拿锁的条件对象notEmpty上唤醒正在等待的1个线程,表示队列里还有1条数据,可以进行消费
}

LinkedBlockingQueue的添加数据方法add,put,offer跟ArrayBlockingQueue一样,不同的是它们的底层实现不一样。

ArrayBlockingQueue中放入数据阻塞的时候,需要消费数据才能唤醒。

而LinkedBlockingQueue中放入数据阻塞的时候,因为它内部有2个锁,可以并行执行放入数据和消费数据,不仅在消费数据的时候进行唤醒插入阻塞的线程,同时在插入的时候如果容量还没满,也会唤醒插入阻塞的线程。

enqueue()真正往单链表中增加元素的方法:

    private void enqueue(Node<E> node) {
// assert putLock.isHeldByCurrentThread();
// assert last.next == null;
last = last.next = node;
}

2.4、出队

LinkedBlockingQueue有不同的几个数据删除方法,poll、take、remove方法。

poll方法:

public E poll() {
final AtomicInteger count = this.count;
if (count.get() == 0) // 如果元素个数为0
return null; // 返回null
E x = null;
int c = -1;
final ReentrantLock takeLock = this.takeLock;
takeLock.lock(); // 拿锁加锁,保证调用poll方法的时候只有1个线程
try {
if (count.get() > 0) { // 判断队列里是否还有数据
x = dequeue(); // 删除头结点
c = count.getAndDecrement(); // 元素个数-1
if (c > 1) // 如果队列里还有元素
notEmpty.signal(); // 在拿锁的条件对象notEmpty上唤醒正在等待的线程,表示队列里还有数据,可以再次消费
}
} finally {
takeLock.unlock(); // 释放拿锁,让其他线程可以调用poll方法
}
if (c == capacity) // 由于存在放锁和拿锁,这里可能放锁一直在添加数据,count会变化。这里的if条件表示如果队列中还可以再插入数据
signalNotFull(); // 在放锁的条件对象notFull上唤醒正在等待的1个线程,表示队列里还能再次添加数据
return x;
}

take方法:

public E take() throws InterruptedException {
E x;
int c = -1;
final AtomicInteger count = this.count;
final ReentrantLock takeLock = this.takeLock;
takeLock.lockInterruptibly(); // 拿锁加锁,保证调用take方法的时候只有1个线程
try {
while (count.get() == 0) { // 如果队列里已经没有元素了
notEmpty.await(); // 阻塞并挂起当前线程
}
x = dequeue(); // 删除头结点
c = count.getAndDecrement(); // 元素个数-1
if (c > 1) // 如果队列里还有元素
notEmpty.signal(); // 在拿锁的条件对象notEmpty上唤醒正在等待的线程,表示队列里还有数据,可以再次消费
} finally {
takeLock.unlock(); // 释放拿锁,让其他线程可以调用take方法
}
if (c == capacity) // 由于存在放锁和拿锁,这里可能放锁一直在添加数据,count会变化。这里的if条件表示如果队列中还可以再插入数据
signalNotFull(); // 在放锁的条件对象notFull上唤醒正在等待的1个线程,表示队列里还能再次添加数据
return x;
}

remove方法:

public boolean remove(Object o) {
if (o == null) return false;
fullyLock(); // remove操作要移动的位置不固定,2个锁都需要加锁
try {
for (Node<E> trail = head, p = trail.next; // 从链表头结点开始遍历
p != null;
trail = p, p = p.next) {
if (o.equals(p.item)) { // 判断是否找到对象
unlink(p, trail); // 修改节点的链接信息,同时调用notFull的signal方法
return true;
}
}
return false;
} finally {
fullyUnlock(); // 2个锁解锁
}
}

LinkedBlockingQueue的take方法对于没数据的情况下会阻塞,poll方法删除链表头结点,remove方法删除指定的对象。

需要注意的是remove方法由于要删除的数据的位置不确定,需要2个锁同时加锁

    /**
* Locks to prevent both puts and takes.
*/
void fullyLock() {
putLock.lock();
takeLock.lock();
} /**
* Unlocks to allow both puts and takes.
*/
void fullyUnlock() {
takeLock.unlock();
putLock.unlock();
}

真正出队的方法:

    private E dequeue() {
// assert takeLock.isHeldByCurrentThread();
// assert head.item == null;
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;
}

2.5、peek操作

获取但是不移除当前队列的头元素,没有则返回null

    public E peek() {
//队列空,则返回null
if (count.get() == 0)
return null;
final ReentrantLock takeLock = this.takeLock;
takeLock.lock();
try {
Node<E> first = head.next;
if (first == null)
return null;
else
return first.item;
} finally {
takeLock.unlock();
}
}

2.6、size方法

public int size() {
return count.get();
}

2.7、完整源码

public class LinkedBlockingQueue<E> extends AbstractQueue<E> implements BlockingQueue<E>, java.io.Serializable {
/**
* 链表节点node类结构
*/
static class Node<E> {
volatile E item;//volatile使得所有的write happen-befor read,保证了数据的可见性
Node<E> next;
Node(E x) { item = x; }
}
/** 队列容量,默认为Integer.MAX_VALUE*/
private final int capacity;
/** 用原子变量 表示当前元素的个数 */
private final AtomicInteger count = new AtomicInteger(0);
/** 表头节点 */
private transient Node<E> head;
/** 表尾节点 */
private transient Node<E> last;
/** 获取元素或删除元素时 要加的takeLock锁 */
private final ReentrantLock takeLock = new ReentrantLock();
/** 获取元素 notEmpty条件 */
private final Condition notEmpty = takeLock.newCondition();
/** 插入元素时 要加putLock锁 */
private final ReentrantLock putLock = new ReentrantLock();
/** 插入时,要判满 */
private final Condition notFull = putLock.newCondition();
/**
* 唤醒等待的take操作,在put/offer中调用(因为这些操作中不会用到takeLock锁)
*/
private void signalNotEmpty() {
final ReentrantLock takeLock = this.takeLock;
takeLock.lock();
try {
notEmpty.signal();
} finally {
takeLock.unlock();
}
}
/**
* 唤醒等待插入操作,在take/poll中调用.
*/
private void signalNotFull() {
final ReentrantLock putLock = this.putLock;
putLock.lock();
try {
notFull.signal();
} finally {
putLock.unlock();
}
}
/**
* 插入到尾部
*/
private void insert(E x) {
last = last.next = new Node<E>(x);
}
/**
* 获取并移除头元素
*/
private E extract() {
Node<E> first = head.next;
head = first;
E x = first.item;
first.item = null;
return x;
}
/**
* 锁住两把锁,在remove,clear等方法中调用
*/
private void fullyLock() {
putLock.lock();
takeLock.lock();
}
/**
* 和fullyLock成对使用
*/
private void fullyUnlock() {
takeLock.unlock();
putLock.unlock();
}
/**
* 默认构造,容量为 Integer.MAX_VALUE
*/
public LinkedBlockingQueue() {
this(Integer.MAX_VALUE);
}
/**
*指定容量的构造
*/
public LinkedBlockingQueue(int capacity) {
if (capacity <= 0) throw new IllegalArgumentException();
this.capacity = capacity;
last = head = new Node<E>(null);
}
/**
* 指定初始化集合的构造
*/
public LinkedBlockingQueue(Collection<? extends E> c) {
this(Integer.MAX_VALUE);
for (E e : c)
add(e);
}
/**
* 通过原子变量,直接获得大小
*/
public int size() {
return count.get();
}
/**
*返回理想情况下(没有内存和资源约束)此队列可接受并且不会被阻塞的附加元素数量。
*/
public int remainingCapacity() {
return capacity - count.get();
}
/**
* 将指定元素插入到此队列的尾部,如有必要,则等待空间变得可用。
*/
public void put(E e) throws InterruptedException {
if (e == null) throw new NullPointerException();
int c = -1;
final ReentrantLock putLock = this.putLock;
final AtomicInteger count = this.count;
putLock.lockInterruptibly();
try {
try {
while (count.get() == capacity)
notFull.await();
} catch (InterruptedException ie) {
notFull.signal(); // propagate to a non-interrupted thread
throw ie;
}
insert(e);
c = count.getAndIncrement();
if (c + 1 < capacity)
notFull.signal();
} finally {
putLock.unlock();
}
if (c == 0)
signalNotEmpty();
}
/**
* 将指定元素插入到此队列的尾部,如有必要,则等待指定的时间以使空间变得可用。
*/
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 {
for (;;) {
if (count.get() < capacity) {
insert(e);
c = count.getAndIncrement();
if (c + 1 < capacity)
notFull.signal();
break;
}
if (nanos <= 0)
return false;
try {
nanos = notFull.awaitNanos(nanos);
} catch (InterruptedException ie) {
notFull.signal(); // propagate to a non-interrupted thread
throw ie;
}
}
} finally {
putLock.unlock();
}
if (c == 0)
signalNotEmpty();
return true;
}
/**
*将指定元素插入到此队列的尾部(如果立即可行且不会超出此队列的容量),
*在成功时返回 true,如果此队列已满,则返回 false。
*/
public boolean offer(E e) {
if (e == null) throw new NullPointerException();
final AtomicInteger count = this.count;
if (count.get() == capacity)
return false;
int c = -1;
final ReentrantLock putLock = this.putLock;
putLock.lock();
try {
if (count.get() < capacity) {
insert(e);
c = count.getAndIncrement();
if (c + 1 < capacity)
notFull.signal();
}
} finally {
putLock.unlock();
}
if (c == 0)
signalNotEmpty();
return c >= 0;
}
//获取并移除此队列的头部,在元素变得可用之前一直等待(如果有必要)。
public E take() throws InterruptedException {
E x;
int c = -1;
final AtomicInteger count = this.count;
final ReentrantLock takeLock = this.takeLock;
takeLock.lockInterruptibly();
try {
try {
while (count.get() == 0)
notEmpty.await();
} catch (InterruptedException ie) {
notEmpty.signal(); // propagate to a non-interrupted thread
throw ie;
}
x = extract();
c = count.getAndDecrement();
if (c > 1)
notEmpty.signal();
} finally {
takeLock.unlock();
}
if (c == capacity)
signalNotFull();
return x;
} //获取并移除此队列的头部,在指定的等待时间前等待可用的元素(如果有必要
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 {
for (;;) {
if (count.get() > 0) {
x = extract();
c = count.getAndDecrement();
if (c > 1)
notEmpty.signal();
break;
}
if (nanos <= 0)
return null;
try {
nanos = notEmpty.awaitNanos(nanos);
} catch (InterruptedException ie) {
notEmpty.signal(); // propagate to a non-interrupted thread
throw ie;
}
}
} finally {
takeLock.unlock();
}
if (c == capacity)
signalNotFull();
return x;
} //获取并移除此队列的头,如果此队列为空,则返回 null。
public E poll() {
final AtomicInteger count = this.count;
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 = extract();
c = count.getAndDecrement();
if (c > 1)
notEmpty.signal();
}
} finally {
takeLock.unlock();
}
if (c == capacity)
signalNotFull();
return x;
}
//获取但不移除此队列的头;如果此队列为空,则返回 null。
public E peek() {
if (count.get() == 0)
return null;
final ReentrantLock takeLock = this.takeLock;
takeLock.lock();
try {
Node<E> first = head.next;
if (first == null)
return null;
else
return first.item;
} finally {
takeLock.unlock();
}
}
/**
* 从此队列移除指定元素的单个实例(如果存在)。
*/
public boolean remove(Object o) {
if (o == null) return false;
boolean removed = false;
fullyLock();
try {
Node<E> trail = head;
Node<E> p = head.next;
while (p != null) {
if (o.equals(p.item)) {
removed = true;
break;
}
trail = p;
p = p.next;
}
if (removed) {
p.item = null;
trail.next = p.next;
if (last == p)
last = trail;
if (count.getAndDecrement() == capacity)
notFull.signalAll();
}
} finally {
fullyUnlock();
}
return removed;
}
……
}

三、使用示例

阻塞队列之五:LinkedBlockingQueue的更多相关文章

  1. 11.并发包阻塞队列之LinkedBlockingQueue

    在上文<10.并发包阻塞队列之ArrayBlockingQueue>中简要解析了ArrayBlockingQueue部分源码,在本文中同样要介绍的是Java并发包中的阻塞队列LinkedB ...

  2. java阻塞队列之LinkedBlockingQueue

    LinkedBlockingQueue是BlockingQueue中的其中一个,其实现方式为单向链表,下面看其具体实现.(均为JDK8) 一.构造函数 在LinkedBlockingQueue中有三个 ...

  3. 阻塞队列之LinkedBlockingQueue

    概述 LinkedBlockingQueue内部由单链表实现,只能从head取元素,从tail添加元素.添加元素和获取元素都有独立的锁,也就是说LinkedBlockingQueue是读写分离的,读写 ...

  4. 用Java如何设计一个阻塞队列,然后说说ArrayBlockingQueue和LinkedBlockingQueue

    前言 用Java如何设计一个阻塞队列,这个问题是在面滴滴的时候被问到的.当时确实没回答好,只是说了用个List,然后消费者再用个死循环一直去监控list的是否有值,有值的话就处理List里面的内容.回 ...

  5. Java中的阻塞队列

    1. 什么是阻塞队列? 阻塞队列(BlockingQueue)是一个支持两个附加操作的队列.这两个附加的操作是:在队列为空时,获取元素的线程会等待队列变为非空.当队列满时,存储元素的线程会等待队列可用 ...

  6. java并发编程学习: 阻塞队列 使用 及 实现原理

    队列(Queue)与栈(Stack)是数据结构中的二种常用结构,队列的特点是先进先出(First In First Out),而Stack是先进后出(First In Last Out),说得通俗点: ...

  7. java并发:阻塞队列

    第一节 阻塞队列 1.1 初识阻塞队列 队列以一种先进先出的方式管理数据,阻塞队列(BlockingQueue)是一个支持两个附加操作的队列,这两个附加的操作是:在队列为空时,获取元素的线程会等待队列 ...

  8. 聊聊并发(七)——Java中的阻塞队列

    3. 阻塞队列的实现原理 聊聊并发(七)--Java中的阻塞队列 作者 方腾飞 发布于 2013年12月18日 | ArchSummit全球架构师峰会(北京站)2016年12月02-03日举办,了解更 ...

  9. Java并发编程(十二)-- 阻塞队列

    在介绍Java的阻塞队列之前,我们简单介绍一下队列. 队列 队列是一种数据结构.它有两个基本操作:在队列尾部加人一个元素,和从队列头部移除一个元素就是说,队列以一种先进先出的方式管理数据,如果你试图向 ...

随机推荐

  1. eureka-8-Eureka 的健康检查

    eureka.client.healthcheck.enabled:true 应用程序将自己的健康状态传播到Eureka Server

  2. windows下类似Linux下的grep命令

    今天要查看windws下代理服务器有哪些IP连接过来,但使用 netstat -na 后出现很多连接会话,不方便查看. 想到Linux下的grep非常方便,于是网络上搜寻,还是有类似的命令findst ...

  3. Slice Header中的field_pic_flag的含义?

    编码模式指帧编码.场编码.帧场自适应编码.当这个句法元素取值为1时属于场编码:0为非场编码. 序列参数集中的句法元素frame_mbs_only_flag和mb_adaptive_frame_fiel ...

  4. OPEN(SAP) UI5 学习入门系列之一:扫盲与热身(下)

    1 UI5代码结构 上一次我们一起用了20秒的时间完成一个UI5版的Hello World.应用打开后有一个按钮,按钮的文字是Hello World,点击这个按钮之后,按钮会慢慢的消失掉(Fade o ...

  5. TCP、UDP详解

    OSI 计算机网络7层模型 TCP/IP四层网络模型 传输层提供应用间的逻辑通信(端到端),网络层提供的是主机到主机的通信,传输层提供的是可靠服务. TCP 中常说的握手指的是:连接的定义和连接的建立 ...

  6. LARAVEL IOC容器 示例解析

    <?php class People { public $dog = null; public function __construct() { $this->dog = new Dog( ...

  7. matlab 相关系数的计算

    1. 首先说说自相关和互相关的概念.     这 个是信号分析里的概念,他们分别表示的是两个时间序列之间和同一个时间序列在任意两个不同时刻的取值之间的相关程度,即互相关函数是描述随机信号 x(t),y ...

  8. Java并发--进程与线程由来

    下面是本文的目录大纲: 一.操作系统中为什么会出现进程? 二.为什么会出现线程? 三.多线程并发 一.操作系统中为什么会出现进程? 说起进程的由来,我们需要从操作系统的发展历史谈起. 也许在今天,我们 ...

  9. SSZipArchive使用详解

    下载SSZipArchive,点击我.或者自己在这里下载. SSZipArchive功能: 解压zip文件 解压密码保护的zip文件 创建zip文件 追加到zip文件 压缩文件 使用一个名字来压缩NS ...

  10. #ifndef/#define/#endif以及#if defined/#else/#endif使用详解

    1.#ifndef_WINDOWS_PLAYM4_H_      #define _WINDOWS_PLAYM4_H_ #endif 问题:想必很多人都看过"头文件中的 #ifndef/#d ...