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

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

  1. public boolean offer(E e) {
  2. if (e == null) throw new NullPointerException();
  3. final AtomicInteger count = this.count;
  4. if (count.get() == capacity)
  5. return false;
  6. int c = -1;
  7. Node<E> node = new Node(e);
  8. final ReentrantLock putLock = this.putLock;
  9. putLock.lock();
  10. try {
  11. if (count.get() < capacity) {
  12. enqueue(node);
  13. c = count.getAndIncrement();
  14. if (c + 1 < capacity)
  15. notFull.signal();
  16. }
  17. } finally {
  18. putLock.unlock();
  19. }
  20. if (c == 0)
  21. signalNotEmpty();
  22. return c >= 0;
  23. }

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

  1. public E poll() {
  2. final AtomicInteger count = this.count;
  3. if (count.get() == 0)
  4. return null;
  5. E x = null;
  6. int c = -1;
  7. final ReentrantLock takeLock = this.takeLock;
  8. takeLock.lock();
  9. try {
  10. if (count.get() > 0) {
  11. x = dequeue();
  12. c = count.getAndDecrement();
  13. if (c > 1)
  14. notEmpty.signal();
  15. }
  16. } finally {
  17. takeLock.unlock();
  18. }
  19. if (c == capacity)
  20. signalNotFull();
  21. return x;
  22. }

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

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

  1. /** Lock held by take, poll, etc */
  2. private final ReentrantLock takeLock = new ReentrantLock();
  3.  
  4. /** Wait queue for waiting takes */
  5. private final Condition notEmpty = takeLock.newCondition();
  6.  
  7. /** Lock held by put, offer, etc */
  8. private final ReentrantLock putLock = new ReentrantLock();
  9.  
  10. /** Wait queue for waiting puts */
  11. private final Condition notFull = putLock.newCondition();

接下来看看put方法:

  1. public void put(E e) throws InterruptedException {
  2. if (e == null) throw new NullPointerException();
  3. // Note: convention in all put/take/etc is to preset local var
  4. // holding count negative to indicate failure unless set.
  5. int c = -1;
  6. Node<E> node = new Node(e);
  7. final ReentrantLock putLock = this.putLock;
  8. final AtomicInteger count = this.count;
  9. putLock.lockInterruptibly();
  10. try {
  11. /*
  12. * Note that count is used in wait guard even though it is
  13. * not protected by lock. This works because count can
  14. * only decrease at this point (all other puts are shut
  15. * out by lock), and we (or some other waiting put) are
  16. * signalled if it ever changes from capacity. Similarly
  17. * for all other uses of count in other wait guards.
  18. */
  19. while (count.get() == capacity) {
  20. notFull.await();
  21. }
  22. enqueue(node);
  23. c = count.getAndIncrement();
  24. if (c + 1 < capacity)
  25. notFull.signal();
  26. } finally {
  27. putLock.unlock();
  28. }
  29. if (c == 0)
  30. signalNotEmpty();
  31. }

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

  1. public E take() throws InterruptedException {
  2. E x;
  3. int c = -1;
  4. final AtomicInteger count = this.count;
  5. final ReentrantLock takeLock = this.takeLock;
  6. takeLock.lockInterruptibly();
  7. try {
  8. while (count.get() == 0) {
  9. notEmpty.await();
  10. }
  11. x = dequeue();
  12. c = count.getAndDecrement();
  13. if (c > 1)
  14. notEmpty.signal();
  15. } finally {
  16. takeLock.unlock();
  17. }
  18. if (c == capacity)
  19. signalNotFull();
  20. return x;
  21. }

最后说说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. Akka(28): Http:About Akka-Http

    众所周知,Akka系统是基于Actor模式的分布式运算系统,非常适合构建大数据平台.所以,无可避免地会出现独立系统之间.与异类系统.与移动系统集成的需求.由于涉及到异类和移动系统,系统对接的方式必须在 ...

  2. win10 删除设备和驱动器中你不要的图标

    设备和驱动器可能有很多你不想要的东西,360云盘,百度网盘,微云-- 删除设备和驱动器中的百度云图标,360网盘图标,要去注册表 运行 regedit 点开 HKEY_CURRENT_USER\SOF ...

  3. JS中的类型识别

    JS为弱类型语言,所以类型识别对JS而言尤为重要,JS中常用的类型识别方法有4种:typeof.Object.prototype.toString.constructor和instanceof. (1 ...

  4. c++中vector的pair与make_pair的使用,双关键字排序

    #include <vector> #include <iostream> #include <algorithm> using namespace std; bo ...

  5. php7+apache2.4配置

    因为需要搭建一套discuz的程序,所以就扯到了php. 对于php完全没接触过,然后就百度了下php的容器,看到apache可以.机子上刚好有装,就干脆用它了. 测试环境:win10x64+php7 ...

  6. linux 生成随机密码和wordlist常用方法

    注:文章内容来自网络收集 关于下面这10个方法,估计很多人也知道了,这里也是为了自己以后用收集一下,不过顺便吐槽下,google第一页,只要是“linux 随机密码”这几个类似的关键字,蹦出来的全特么 ...

  7. CountDownLatch、CyclicBarrier和 Semaphore比较

    1)CountDownLatch和CyclicBarrier都能够实现线程之间的等待,只不过它们侧重点不同: CountDownLatch一般用于某个线程A等待若干个其他线程执行完任务之后,它才执行: ...

  8. web前端工程师全套教程免费分享

    这是我自己早前听课时整理的前端全套知识点,适用于初学者,也可以适用于中级的程序员,你们可以下载下来.我自认为还是比较系统全面的,可以抵得上市场上90%的学习资料.讨厌那些随便乱写的资料还有拿出来卖钱的 ...

  9. poj 2720 Last Digits

    Last Digits Time Limit: 1000MS   Memory Limit: 65536K Total Submissions: 2233   Accepted: 474 Descri ...

  10. 关于在 IntellIj IDEA中JSP页面 cannot resolve method getParameter("")的解决方案

    File->Project Structure->Libraries,然后点加号,将Tomcat lib文件夹下的servlet.jar和servlet-api.jar包导入.