JUC源码分析-集合篇(六)LinkedBlockingQueue

1. 数据结构

LinkedBlockingQueue 和 ConcurrentLinkedQueue 一样都是由 head 节点和 last 节点组成,每个节点(Node)由节点元素(item)和指向下一个节点(next)的引用组成,节点与节点之间就是通过这个 next 关联起来,从而组成一张链表结构的队列。默认情况下 head 节点存储的元素为空,last 节点等于 head 节点。和 ConcurrentLinkedQueue 不同的是 LinkedBlockingQueue 是基于 ReentrantLock 锁实现的,因此 head、last 以及 Node.item、Node.next 都不用 volatile 修辞。

// head.item == null
transient Node<E> head;
// last.next == null
private transient Node<E> last; private static class Node<E> {
E item;
Node<E> next;
}

默认情况下 head、last 都是空节点。

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);
}

2. 基于 ReentrantLock 的实现

private final ReentrantLock takeLock = new ReentrantLock();
// 集合已空则调用notEmpty.await,等集合添加元素后调用notEmpty.singal
private final Condition notEmpty = takeLock.newCondition(); private final ReentrantLock putLock = new ReentrantLock();
// 集合已满则调用notFull.await,等集合取出元素后调用notFull.singal
private final Condition notFull = putLock.newCondition();

3. 入队 offer

和 ConcurrentLinkedQueue 不同,last 是实时指向尾节点的,也就是每次插入元素时都会更新尾节点。代码如下

// offer 非阻塞
public boolean offer(E e) {
if (e == null) throw new NullPointerException();
final AtomicInteger count = this.count;
if (count.get() == capacity)
return false;
// 1. c表示插入前元素的个数
int c = -1;
Node<E> node = new Node<E>(e);
final ReentrantLock putLock = this.putLock;
putLock.lock();
try {
// 2. 元素入队有2个操作:一是元素添加到last.next并更新last;
// 二是唤醒阻塞的put操作继续添加元素(只有put时会阻塞notFull.await)
if (count.get() < capacity) {
// 2.1 元素入队
enqueue(node);
// 2.2 c表示插入前元素的个数
c = count.getAndIncrement();
// 2.3 集合未满,唤醒put操作,继续添加元素
if (c + 1 < capacity)
notFull.signal();
}
} finally {
putLock.unlock();
}
// 3. 插入前集合为空,则唤醒take操作,可以取元素了
if (c == 0)
signalNotEmpty();
return c >= 0;
}

元素入队 enqueue 有两个操作:一是 last.next 节点指向 node;二是 last 指向新的尾节点 node。也就是说 last 一定是指向尾节点的。

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

4. 出队 poll

// poll 非阻塞
public E poll() {
final AtomicInteger count = this.count;
if (count.get() == 0)
return null;
E x = null;
// 1. poll操作前元素的个数
int c = -1;
final ReentrantLock takeLock = this.takeLock;
takeLock.lock();
try {
// 2. 元素出队有2个操作:一是head.next出队
// 二是唤醒阻塞的take操作继续取出元素(只有take时会阻塞notEmpty.await)
if (count.get() > 0) {
// 2.1 head.next出队
x = dequeue();
// 2.2 c为poll前元素的个数
c = count.getAndDecrement();
// 2.3 集合中元素不为空,唤醒take操作,断续取元素
if (c > 1)
notEmpty.signal();
}
} finally {
takeLock.unlock();
}
// 3. 取元素前集合已满,则唤醒put操作,可以继续添加元素
if (c == capacity)
signalNotFull();
return x;
}

元素出队 dequeue 有三个操作:一是 head.next 出队;二是 head.next 指向自己,等待 GC 回收;三是修改 head 节点。

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;
}

5. 删除元素 remove

// 删除指定 value 的元素
public boolean remove(Object o) {
if (o == null) return false;
fullyLock();
try {
for (Node<E> trail = head, p = trail.next;
p != null;
trail = p, p = p.next) {
if (o.equals(p.item)) {
// 删除指定节点 p,其中 trail 为 p 的前驱节点
unlink(p, trail);
return true;
}
}
return false;
} finally {
fullyUnlock();
}
} // 删除指定节点 p,其中 trail 为 p 的前驱节点
// 注意 p.next 没变
void unlink(Node<E> p, Node<E> trail) {
// assert isFullyLocked();
// p.next is not changed, to allow iterators that are
// traversing p to maintain their weak-consistency guarantee.
p.item = null;
trail.next = p.next;
if (last == p)
last = trail;
if (count.getAndDecrement() == capacity)
notFull.signal();
}

5. 将集合中的元素取出 drainTo

// 将集合中的全部元素取出到集合 c 中
public int drainTo(Collection<? super E> c) {
return drainTo(c, Integer.MAX_VALUE);
} // 将集合中的 maxElements 个元素取出到集合 c 中
public int drainTo(Collection<? super E> c, int maxElements) {
if (c == null)
throw new NullPointerException();
if (c == this)
throw new IllegalArgumentException();
if (maxElements <= 0)
return 0;
boolean signalNotFull = false;
final ReentrantLock takeLock = this.takeLock;
takeLock.lock();
try {
int n = Math.min(maxElements, count.get());
// count.get provides visibility to first n Nodes
Node<E> h = head;
int i = 0;
try {
while (i < n) {
Node<E> p = h.next;
c.add(p.item);
p.item = null;
h.next = h;
h = p;
++i;
}
return n;
} finally {
if (i > 0) {
// assert h.item == null;
head = h;
signalNotFull = (count.getAndAdd(-i) == capacity);
}
}
} finally {
takeLock.unlock();
if (signalNotFull)
signalNotFull();
}
}

每天用心记录一点点。内容也许不重要,但习惯很重要!

JUC源码分析-集合篇(六)LinkedBlockingQueue的更多相关文章

  1. JUC源码分析-集合篇(十)LinkedTransferQueue

    JUC源码分析-集合篇(十)LinkedTransferQueue LinkedTransferQueue(LTQ) 相比 BlockingQueue 更进一步,生产者会一直阻塞直到所添加到队列的元素 ...

  2. JUC源码分析-集合篇:并发类容器介绍

    JUC源码分析-集合篇:并发类容器介绍 同步类容器是 线程安全 的,如 Vector.HashTable 等容器的同步功能都是由 Collections.synchronizedMap 等工厂方法去创 ...

  3. JUC源码分析-集合篇(七)PriorityBlockingQueue

    JUC源码分析-集合篇(七)PriorityBlockingQueue PriorityBlockingQueue 是带优先级的无界阻塞队列,每次出队都返回优先级最高的元素,是二叉树最小堆的实现. P ...

  4. JUC源码分析-集合篇(三)ConcurrentLinkedQueue

    JUC源码分析-集合篇(三)ConcurrentLinkedQueue 在并发编程中,有时候需要使用线程安全的队列.如果要实现一个线程安全的队列有两种方式:一种是使用阻塞算法,另一种是使用非阻塞算法. ...

  5. JUC源码分析-集合篇(九)SynchronousQueue

    JUC源码分析-集合篇(九)SynchronousQueue SynchronousQueue 是一个同步阻塞队列,它的每个插入操作都要等待其他线程相应的移除操作,反之亦然.SynchronousQu ...

  6. JUC源码分析-集合篇(八)DelayQueue

    JUC源码分析-集合篇(八)DelayQueue DelayQueue 是一个支持延时获取元素的无界阻塞队列.队列使用 PriorityQueue 来实现. 队列中的元素必须实现 Delayed 接口 ...

  7. JUC源码分析-集合篇(四)CopyOnWriteArrayList

    JUC源码分析-集合篇(四)CopyOnWriteArrayList Copy-On-Write 简称 COW,是一种用于程序设计中的优化策略.其基本思路是,从一开始大家都在共享同一个内容,当某个人想 ...

  8. JUC源码分析-集合篇(一)ConcurrentHashMap

    JUC源码分析-集合篇(一)ConcurrentHashMap 1. 概述 <HashMap 源码详细分析(JDK1.8)>:https://segmentfault.com/a/1190 ...

  9. JUC源码分析-线程池篇(三)ScheduledThreadPoolExecutor

    JUC源码分析-线程池篇(三)ScheduledThreadPoolExecutor ScheduledThreadPoolExecutor 继承自 ThreadPoolExecutor.它主要用来在 ...

随机推荐

  1. 发送验证码倒计时60s

    var wait=60; function time(o) { if (wait == 0) { o.removeClass("gray"); o.text("发送验证码 ...

  2. MySQL配置(二)

    上篇文章简单的讲了一下MySQL的配置,这章我在具体讲述一下我所配置的一些内容. 一.密码策略        MySQL5.7默认安装了密码安全检查的插件.默认密码检查策略要求密码必须包含:大小写字母 ...

  3. Python文章导航

    1.Python+Eclipse安装.配置: http://www.cnblogs.com/rhyswang/p/8087752.html 2.数学运算及math库: http://www.cnblo ...

  4. Cai_Sublime

    Cai_Sublime Package Control:插件包管理工具 The simplest method of installation is through the Sublime Text ...

  5. 去BAT面试完的Mysql面试题总结(55道,带完整答案)

    1.一张表里面有ID自增主键,当insert了17条记录之后,删除了第15,16,17条记录,再把mysql重启,再insert一条记录,这条记录的ID是18还是15 ? 2.mysql的技术特点是什 ...

  6. 28-python基础-python3-列表多重赋值

    1-列表多重赋值常规方法 >>> cat = ['fat', 'black', 'loud'] >>> size = cat[0] >>> col ...

  7. 10个艰难的Java面试题与答案

    10个最难回答的Java面试题 这是我收集的10个较难回答的 Java 面试题.这些问题主要来自 Java 核心部分 ,不涉及 Java EE 相关问题.这些问题都是容易在各种 Java 面试中被问到 ...

  8. JS浏览器事件循环机制

    文章来自我的 github 博客,包括技术输出和学习笔记,欢迎star. 先来明白些概念性内容. 进程.线程 进程是系统分配的独立资源,是 CPU 资源分配的基本单位,进程是由一个或者多个线程组成的. ...

  9. 《node.js开发指南》partial is not defined的解决方案

    由于ejs的升级,<node.js开发指南>中使用的 partial 函数已经摒弃,使用foreach,include代替 原来的代码是: <%- partial('listitem ...

  10. 面试经典:链表中倒数第k个结点?如何从大量数据中找出高频词?

    记录两道面试题: 题目描述: 输入一个链表,输出该链表中倒数第k个结点.(单向链表) 拿到这个问题的时候自然而然会想到让链表从末尾开始next   K-1 次不就是第K-1个节点了么,但是必须要注意一 ...