问题

(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. 微信小程序入门三实战

    微信小应用借鉴了很多web的理念,但是其与传统的webApp.微信公共号这些BS架构不同,他是CS架构,是客户端的程序 小程序开发实战--豆瓣电影 项目配置 -在app.jsop中进行简单配置 --n ...

  2. RabbitMQ windows安装(一 )

    RabbitMQ 简单介绍: 是可以实现应用程序的解耦和异步,也可用作消息缓冲和消息分发的消息队列(MQ): 安装: 1.安装RabbitMQ前先安装Erlang语言开发包,下载地址:http://w ...

  3. 基于分支限界法的旅行商问题(TSP)二

    和上篇一样,考前写写伪代码,考完了补上具体的解释和代码. 状态{矩阵,结果集,下界} 全局结果集列表,全局上界初始为Infinite 建立一个heap,存储状态,出堆规则为拥有最小的下界. 利用red ...

  4. 万网主机使用wordpress发送邮件的方法

    今天弄了一下午总算明白了,这里写一下具体过程. 首先是邮箱,万网主机是不支持mail()函数的,所以默认的不可用,如果你想发送邮件的话,只能使用fsockopen()函数.首先进入万网主机管理平台,启 ...

  5. Django rest framework(6)----序列化

    目录 Django rest framework(1)----认证 Django rest framework(2)----权限 Django rest framework(3)----节流 Djan ...

  6. 如何书写一篇能看懂的html和CSS代码

    在书写html和CSS过程中,如果只是想要实现网页的外观和基本功能,那么简单的书写代码就可以满足需求,甚至不需要使用类名或者注释等.但实际上,这么写肯定是不行的,首先对于类同结构的重复书写就是一件很浪 ...

  7. new/delete和malloc/free的比较

    从C++角度上说,使用new分配堆空间可以调用类的构造函数,而malloc()函数仅仅是一个函数调用,它不会调用构造函数,它所接受的参数是一个unsigned long类型.同样,delete在释放堆 ...

  8. PAT1041: Be Unique

    1041. Be Unique (20) 时间限制 100 ms 内存限制 65536 kB 代码长度限制 16000 B 判题程序 Standard 作者 CHEN, Yue Being uniqu ...

  9. activemq的消息确认机制ACK

    一.简介 消息消费者有没有接收到消息,需要有一种机制让消息提供者知道,这个机制就是消息确认机制. ACK(Acknowledgement)即确认字符,在数据通信中,接收站发给发送站的一种传输类控制字符 ...

  10. 使用WordPress搭建的网站如何使URL中不出现”wordpress”

    问题描述: 当我们在服务器中安装WordPress时,通常,WordPress默认是将WordPress的文件安装到了一个名为"wordpress"的文件夹里.这样在一般情况下,如 ...