这篇文章来说说稍微复杂一些的LinkedBlockingQueue。LinkedBlockingQueue使用一个链表来实现,会有一个head和tail分别指向队列的开始和队列的结尾。因此LinkedBlockingQueue会有两把锁,分别控制这两个元素,这样在添加元素和拿走元素的时候就不会有锁的冲突,因此取走元素操作的是head,而添加元素操作的是tail。

老规矩先看offer方法和poll方法

    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);
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方法在添加元素时候仅仅涉及到putLock,但是还是会需要takeLock,看看signalNotEmpty代码就知道。而poll方法拿走元素的时候涉及到takeLock,也是会需要putLock。参见signalNotFull()。关于signalNotEmpty会在后面讲阻塞的时候讲到。

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

这里顺便说说队列长度的count,因为有两把锁存在,所以如果还是像ArrayBlockingQueue一样使用基本类型的count的话会同时用到两把锁,这样就会很复杂,因此直接使用原子数据类型AtomicInteger来操作count。

接下来谈谈阻塞的问题,一个BlockingQueue会有两个Condition:notFull和notEmpty,LinkedBlockingQueue会有两把锁,因此这两个Condition肯定是由这两个锁分别创建的,takeLock创建notEmpty,putLock创建notFull。

    /** 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();

接下来看看put方法:

    public void put(E e) throws InterruptedException {
if (e == null) throw new NullPointerException();
// Note: convention in all put/take/etc is to preset local var
// holding count negative to indicate failure unless set.
int c = -1;
Node<E> node = new Node(e);
final ReentrantLock putLock = this.putLock;
final AtomicInteger count = this.count;
putLock.lockInterruptibly();
try {
/*
* Note that count is used in wait guard even though it is
* not protected by lock. This works because count can
* only decrease at this point (all other puts are shut
* out by lock), and we (or some other waiting put) are
* signalled if it ever changes from capacity. Similarly
* for all other uses of count in other wait guards.
*/
while (count.get() == capacity) {
notFull.await();
}
enqueue(node);
c = count.getAndIncrement();
if (c + 1 < capacity)
notFull.signal();
} finally {
putLock.unlock();
}
if (c == 0)
signalNotEmpty();
}

其实大体逻辑和ArrayBlockingQueue差不多,也会需要通知notEmpty条件,因为notEmpty条件属于takeLock,而调用signal方法需要获取Lock,因此put方法也是用到了另外一个锁:takeLock。这里有一点会不同,按照道理来说put方法是不需要通知notFull条件的,是由由拿走元素的操作来通知的,但是notFull条件属于putLock,而拿走元素时,是用了takeLock,因此这里put方法在拥有putLock的情况通知notFull条件,会让其他添加元素的方法避免过长时间的等待。同理对于take方法来说也通知notEmpty条件。

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

最后说说remove和contains方法,因为需要操作整个链表,因此需要同时拥有两个锁才能操作。

《java.util.concurrent 包源码阅读》07 LinkedBlockingQueue的更多相关文章

  1. 《java.util.concurrent 包源码阅读》 结束语

    <java.util.concurrent 包源码阅读>系列文章已经全部写完了.开始的几篇文章是根据自己的读书笔记整理出来的(当时只阅读了部分的源代码),后面的大部分都是一边读源代码,一边 ...

  2. 《java.util.concurrent 包源码阅读》13 线程池系列之ThreadPoolExecutor 第三部分

    这一部分来说说线程池如何进行状态控制,即线程池的开启和关闭. 先来说说线程池的开启,这部分来看ThreadPoolExecutor构造方法: public ThreadPoolExecutor(int ...

  3. 《java.util.concurrent 包源码阅读》02 关于java.util.concurrent.atomic包

    Aomic数据类型有四种类型:AomicBoolean, AomicInteger, AomicLong, 和AomicReferrence(针对Object的)以及它们的数组类型, 还有一个特殊的A ...

  4. 《java.util.concurrent 包源码阅读》04 ConcurrentMap

    Java集合框架中的Map类型的数据结构是非线程安全,在多线程环境中使用时需要手动进行线程同步.因此在java.util.concurrent包中提供了一个线程安全版本的Map类型数据结构:Concu ...

  5. 《java.util.concurrent 包源码阅读》17 信号量 Semaphore

    学过操作系统的朋友都知道信号量,在java.util.concurrent包中也有一个关于信号量的实现:Semaphore. 从代码实现的角度来说,信号量与锁很类似,可以看成是一个有限的共享锁,即只能 ...

  6. 《java.util.concurrent 包源码阅读》06 ArrayBlockingQueue

    对于BlockingQueue的具体实现,主要关注的有两点:线程安全的实现和阻塞操作的实现.所以分析ArrayBlockingQueue也是基于这两点. 对于线程安全来说,所有的添加元素的方法和拿走元 ...

  7. 《java.util.concurrent 包源码阅读》09 线程池系列之介绍篇

    concurrent包中Executor接口的主要类的关系图如下: Executor接口非常单一,就是执行一个Runnable的命令. public interface Executor { void ...

  8. 《java.util.concurrent 包源码阅读》05 BlockingQueue

    想必大家都很熟悉生产者-消费者队列,生产者负责添加元素到队列,如果队列已满则会进入阻塞状态直到有消费者拿走元素.相反,消费者负责从队列中拿走元素,如果队列为空则会进入阻塞状态直到有生产者添加元素到队列 ...

  9. 《java.util.concurrent 包源码阅读》10 线程池系列之AbstractExecutorService

    AbstractExecutorService对ExecutorService的执行任务类型的方法提供了一个默认实现.这些方法包括submit,invokeAny和InvokeAll. 注意的是来自E ...

随机推荐

  1. 前端笔记----jquery入门知识点总结

    一.jquery的加载方法 $(document).ready(function(){js代码}); $(function(){js代码});(一般使用这个); 注意点1:使用jquery必须先导入函 ...

  2. (转)[疯狂Java]NIO:Channel的map映射

    原文出自:http://blog.csdn.net/lirx_tech/article/details/51396268 1. 通道映射技术: 1) 其实就是一种快速读写技术,它将通道所连接的数据节点 ...

  3. C#通过OpenCL调用显卡GPU做高效并行运算

    GPU的并行运算能力远超CPU,有时候我们会需要用到超大数据并行运算,可以考虑用GPU实现,这是一篇C#调用GPU进行运算的入门教程. 1: 下载相关的库: https://sourceforge.n ...

  4. 主要讲下hack的兼容用法,比较浅,哈哈

    hack是主要来处理IE的兼容,不同的IE,不同的兼容方式 /*   属性前缀法(即类内部Hack):       *color:#000; *号对IE6,IE7都生效   +color:#555; ...

  5. linux命令行下svn常用命令

    linux命令行下svn常用命令 1. 将文件checkout到本地目录 1 #path是服务器上的目录 2 svn checkout path 3 4 #示例 5 svn checkout svn: ...

  6. 做直线不要使用hr

    需要横线.竖线时不要使用hr标签来做,容易出问题,可以使用高度或宽度为1px并加上背景色的div.p代替.

  7. LeetCode 566. Reshape the Matrix (重塑矩阵)

    In MATLAB, there is a very useful function called 'reshape', which can reshape a matrix into a new o ...

  8. 本地文件与服务器文件同步shell脚本。

    #!/bin/sh read -t 30 -p "请输入项目名:" name echo -e "\n" echo "项目名为:$name" ...

  9. Android Weekly Notes Issue #281

    October 29th, 2017 Android Weekly Issue #281 本期内容不多,包含了小众DI库牙签帮助测试的文章,Kotlin中Delegate的强大之介绍,以及基于Goog ...

  10. 关于Makefile,Makefile.in,Makefile.am,Configure功能及相互关系的问题

    makefile写法 在 Unix 上写程式的人大概都碰过 Makefile,尤其是用 C 来开发程式的人.用 make来开发和编译程式的确很方便,可是要写出一个 Makefile就不简单了.偏偏介绍 ...