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. LInux终端中Ctrl+S卡死

    因为初学Linux,在vim中写东西是总是喜欢按Ctrl+s来保存内容导致终端突然卡主,然后上网查资料发现了Ctrl+s 暂停屏幕输出[锁住终端]而对应的按键是Ctrl+q 恢复屏幕输出[解锁终端]

  2. 25. 服务器性能监控之nmon工具介绍

    nmon介绍: nmon是一个简单的性能监测工具,可以监测CPU.内存.网络等的使用情况. 步骤: 1.下载nmon(根据你的操作系统下载),地址 2.nmon文件部署到服务器中 3.启动nmon(注 ...

  3. 第六周实验总结&学习总结

    一.实验目的 (1)掌握类的继承方法 (2)变量的继承和覆盖,方法的继承,重载和覆盖实现 二.实验内容 源代码 package java2; import java.util.Scanner; cla ...

  4. leetcode.位运算.136只出现一次的元素-Java

    1. 具体题目 给定一个非空整数数组,除了某个元素只出现一次以外,其余每个元素均出现两次.找出那个只出现了一次的元素. 说明:你的算法应该具有线性时间复杂度. 你可以不使用额外空间来实现吗? 示例 1 ...

  5. python+tushare获取上市公司财务报表:资产负债表

    接口:balancesheet 描述:获取上市公司资产负债表 积分:用户需要至少500积分才可以调取,具体请参阅本文最下方积分获取办法 注:tushare包下载和初始化教程,请查阅我之前的文章 提示: ...

  6. linux/unix下setuid/seteuid/setreuid/setresuid

    其中setresuid()具有最清晰的语法: setresuid()被执行的条件有: ①当前进程的euid是root ②三个参数,每一个等于原来某个id中的一个 如果满足以上条件的任意一个,setre ...

  7. SpringCloud:基础

    SpringCloud:基础 SpringCloud 是微服务架构的一个实现框架,说他是一个框架更不如说他是一个生态,他包含了很多个技术,将这些技术组合起来形成我们的微服务架构应用. 1.Spring ...

  8. 【转】网站SEO优化中网站的三大标签指的是什么?

    对于很多刚刚接触SEO的新手朋友来说,会经常听到别人提及网站的三大标签.那么,具体什么是网站的三大标签呢?其实网站的三大标签指的就是title.keywords.description,通俗一点也可以 ...

  9. LeetCode Array Easy 66. Plus One

    Description Given a non-empty array of digits representing a non-negative integer, plus one to the i ...

  10. RK3288 android切换耳麦通道

    通过耳机状态切换耳机mic与板子麦/work/rk3288/firefly-rk3288_android5.1_git_20180126/kernel/sound/soc/codecs/es8323. ...