java源码阅读LinkedBlockingQueue
1类签名与简介
public class LinkedBlockingQueue<E> extends AbstractQueue<E>
implements BlockingQueue<E>, java.io.Serializable
LinkedBlockingQueue是Java并发包的成员,该类基于链表实现了阻塞队列。
基于链表的队列通常比基于数组的队列有更高的吞吐量,但是在大多数并发程序中可预测性能较低。(本质是理解链表和数组各自的性能优势)
LinkedBlockingQueue的容量在未指定的情况下是Integer.MAX_VALUE
,也可以自己指定容量,且在指定后不可更改,这样做是防止队列过多的容量扩展。
其构造方法有三个
//创建一个 LinkedBlockingQueue ,容量为 Integer.MAX_VALUE 。
LinkedBlockingQueue() //创建一个 LinkedBlockingQueue ,容量为 Integer.MAX_VALUE ,
//最初包含给定集合的元素,以集合的迭代器的遍历顺序添加。
LinkedBlockingQueue(Collection<? extends E> c) //创建一个具有给定(固定)容量的 LinkedBlockingQueue 。
LinkedBlockingQueue(int capacity)
2数据结构
static class Node<E> {
E item; /**
* One of:
* - the real successor Node
* - this Node, meaning the successor is head.next
* - null, meaning there is no successor (this is the last node)
*/
Node<E> next; Node(E x) { item = x; }
} /** 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; /** 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();
这里把链表的每个节点抽象成了Node内部类,其他的都很好理解。
注意:head节点的item是null,head节点的next才是队列的队头,而last节点就是指向队尾。
但是如果从上一篇ArrayBlockingQueue过来的读者(包括我自己)也许会有几个疑问:
(1)LinkedBlockingQueue的元素计数器count会什么要声明成并发支持的AtomicInteger?而ArrayBlockingQueue的count是int的。
(2)LinkedBlockingQueue为什么需要两个锁(takeLock与putLock)?而ArrayBlockingQueue只要1个就ok了。
下面我们希望能从源码中找到这些答案。
3入队/出队
(1)入队 enqueue
private void enqueue(Node<E> node) {
last = last.next = node;
}
将队尾指向新加入的节点。上面可以拆分为last.next = node和last = last.next
(2)出队 dequeue
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;
}
这里先是删除head指向的节点(item为null),然后将队头的节点变成head节点(将item置null)。dequeue操作的前提是队列不为null。
4常用方法
(1)添加元素(put、offer)
put方法
public void put(E e) throws InterruptedException {
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;
putLock.lockInterruptibly();
try { while (count.get() == capacity) {
notFull.await();
}
enqueue(node);
c = count.getAndIncrement();
if (c + 1 < capacity)
notFull.signal();
} finally {
putLock.unlock();
}
if (c == 0)
signalNotEmpty();
}
put是一个阻塞的入队实现,在队尾插入新元素。这里加了一个putLock锁,所以对于多线程而言put之间是互斥的,也就是说同时只有一个线程执行line8-20。
在添加前判断队列是否满了,若是进入阻塞状态。没满则入队,注意line15 count是先赋值给c然后再增1的,所以line16才会判断c+1,若队列没有满则唤醒1个被notFull.await()阻塞的线程。line21若c==0则表示之前入队的是该队列的仅有的一个元素,在入队一个元素的时候,会调用signalNotEmpty,唤醒一个阻塞的出队线程。(因为刚刚加入元素了,队列不为null了,这是一个临界状态队列由null到非空)
put方法是没有返回值的,offer提供了一个有返回值的实现,插入成功返回true,失败返回false。
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 {
while (count.get() == capacity) {
if (nanos <= 0)
return false;
nanos = notFull.awaitNanos(nanos);
}
enqueue(new Node<E>(e));
c = count.getAndIncrement();
if (c + 1 < capacity)
notFull.signal();
} finally {
putLock.unlock();
}
if (c == 0)
signalNotEmpty();
return true;
}
除了有返回值,阻塞时间限制,这里的offer与前面的put没什么两样。下面还有一个offer的重载版本
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;
Node<E> node = new Node<E>(e);
final ReentrantLock putLock = this.putLock;
putLock.lock();
try {
if (count.get() < capacity) {
enqueue(node);
c = count.getAndIncrement();
if (c + 1 < capacity)
notFull.signal();
}
} finally {
putLock.unlock();
}
if (c == 0)
signalNotEmpty();
return c >= 0;
}
这里的offer不会阻塞,当队列为满时,会直接返回false。
(2)删除元素(take、poll)
take方法
public E take() throws InterruptedException {
E x;
int c = -1;
final AtomicInteger count = this.count;
final ReentrantLock takeLock = this.takeLock;
takeLock.lockInterruptibly();
try {
while (count.get() == 0) {
notEmpty.await();
}
x = dequeue();
c = count.getAndDecrement();
if (c > 1)
notEmpty.signal();
} finally {
takeLock.unlock();
}
if (c == capacity)
signalNotFull();
return x;
}
take方法提供了一个阻塞删除(出队)操作。当队列为空时,会阻塞。若不为空则执行出队操作。每次出队后都会判断此时的队列是否为空,若不为空就会唤醒另一个阻塞的出队线程,另一个又会唤醒下一个直到队列为空。同理,c == capacity是一个临界状态,队列刚从满变成不满,然后去唤醒1个阻塞的入队线程。
看到这里可能就明白了为什么只在临界状态的时候唤醒1个就行了。例如,从满队列出队1个元素,队列就不满了,这时唤醒1个阻塞的入队线程,他执行完入队操作后若不满则会继续唤醒其他阻塞的入队线程,直到下一个临界值。
注意这里的锁是takeLock,入队的锁(putLock)和出队的锁(takeLock)不一样。也就是说入队操做之间是互斥的,出队操作之间也是互斥的,但是入队和出队之间是可以同步的。一个是操作表头,一个是操作表尾,同步似乎是可以理解的。
这里我们可以解决上面预留的第一个问题:count为什么是AtomicInteger的?
因为入队和出队可以同步执行,都可以修改count值,要保证count的自增和自减都是原子的,所以使用AtomicInteger类型。
LinkedBlockingQueue用了两个锁似乎是符合逻辑的,那么ArrayBlockingQueue为什么不这么做?
因为ArrayBlockingQueue是基于数组的循环队列。链表队列删除头部和添加尾部都只和1个节点相关,前面也分析了为了防止添加和移除互相影响,LinkedBlockingQueue维护了一个原子计数器count。而数组位置会循环,最后一个位置的下一个位置是第一个位置,这样的操作无法直接原子话,需要加锁。所以就没有必要了。(也不知道对不对- -!)
同理,删除操作除了take还有poll,poll方法提供了重载版本,一个是阻塞的poll如下
public E poll(long timeout, TimeUnit unit) throws InterruptedException
其原理与take类似,另一个是非阻塞的poll,如下
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 = dequeue();
c = count.getAndDecrement();
if (c > 1)
notEmpty.signal();
}
} finally {
takeLock.unlock();
}
if (c == capacity)
signalNotFull();
return x;
}
该poll不会阻塞,若队列为空,直接返回null,否则出队。
(3)其他
没有其他了!博主很懒,其他的大家自行阅读源码~~
java源码阅读LinkedBlockingQueue的更多相关文章
- Java源码阅读的真实体会(一种学习思路)
Java源码阅读的真实体会(一种学习思路) 刚才在论坛不经意间,看到有关源码阅读的帖子.回想自己前几年,阅读源码那种兴奋和成就感(1),不禁又有一种激动. 源码阅读,我觉得最核心有三点:技术基础+强烈 ...
- Java源码阅读的真实体会(一种学习思路)【转】
Java源码阅读的真实体会(一种学习思路) 刚才在论坛不经意间,看到有关源码阅读的帖子.回想自己前几年,阅读源码那种兴奋和成就感(1),不禁又有一种激动. 源码阅读,我觉得最核心有三点:技术基础+ ...
- 如何阅读Java源码 阅读java的真实体会
刚才在论坛不经意间,看到有关源码阅读的帖子.回想自己前几年,阅读源码那种兴奋和成就感(1),不禁又有一种激动. 源码阅读,我觉得最核心有三点:技术基础+强烈的求知欲+耐心. 说到技术基础,我打个比 ...
- [收藏] Java源码阅读的真实体会
收藏自http://www.iteye.com/topic/1113732 刚才在论坛不经意间,看到有关源码阅读的帖子.回想自己前几年,阅读源码那种兴奋和成就感(1),不禁又有一种激动. 源码阅读,我 ...
- java源码阅读Hashtable
1类签名与注释 public class Hashtable<K,V> extends Dictionary<K,V> implements Map<K,V>, C ...
- Java源码阅读Stack
Stack(栈)实现了一个后进先出(LIFO)的数据结构.该类继承了Vector类,是通过调用父类Vector的方法实现基本操作的. Stack共有以下五个操作: put:将元素压入栈顶. pop:弹 ...
- Java源码阅读顺序
阅读顺序参考链接:https://blog.csdn.net/qq_21033663/article/details/79571506 阅读源码:JDK 8 计划阅读的package: 1.java. ...
- java源码阅读ArrayBlockingQueue
1类签名与简介 public class ArrayBlockingQueue<E> extends AbstractQueue<E> implements BlockingQ ...
- Java源码阅读ArrayList
1简介 public class ArrayList<E> extends AbstractList<E> implements List<E>, RandomAc ...
随机推荐
- Swift, Playgrounds, and XCPlayground
http://www.codeschool.com/blog/2014/12/12/swift-playgrounds-xcplayground/ Swift, Playgrounds, and XC ...
- POI导入导出小案例
一.HSSF 97-2003 需要jar:poi-3.9.jar 简单示例:生成EXCEL //93---2003 String [] titlie={"id","nam ...
- V-Hyper安装ubuntu-13.10-server-amd64
1.在windws8上的V_Hyper虚拟机上安装Ubuntu虚拟机服务器版.遇到的问题和解决方案 2.正确的在V-Hyper配置方法参考文章:在Hyper-V中安装和配置Ubuntu Server ...
- [ Python - 14 ] python进程及线程编程
什么是进程: 简单来讲,进程就是操作系统中运行的程序或任务,进程和程序的区别在于进程是动态的,而程序是静态的.进程是操作系统资源管理的最小单位. 什么是线程: 线程是进程的一个实体,是cpu调度和分派 ...
- docker rancher 安装
1.rancher 中文文档 https://rancher.com/docs/rancher/v1.6/zh/ 2.从阿里云拉取镜像 docker pull registry.cn-hangzhou ...
- Laravel 中的 Many-To-Many
在实际的开发中,我们经常会接触到几种常见的对应关系模式: One-To-One //一对一 One-To-Many //一对多 Many-To-Many //多对多 在刚刚开始接触到这些概念的时候,其 ...
- ubantu16.04服务器错误提示没有安装php_fileinfo扩展
如果你是安装的LNMP1.3full一键安装包,安装的是php5.6.22,你会遇到这个错误,解决方法也不难,请看如下: 不需要去下载扩展,只需要进入此fileinfo目录(我这里有多个版本5.6,7 ...
- AC日记——Keywords Search hdu 2222
2222 思路: ac自动机模板题: 代码: #include <cstdio> #include <cstring> #include <iostream> #i ...
- The number of steps(概率dp)
Description Mary stands in a strange maze, the maze looks like a triangle(the first layer have one r ...
- [BZOJ5020][THUWC2017]在美妙的数学王国中畅游(LCT)
5020: [THUWC 2017]在美妙的数学王国中畅游 Time Limit: 80 Sec Memory Limit: 512 MBSec Special JudgeSubmit: 323 ...