Java阻塞队列实现原理分析

Java中的阻塞队列接口BlockingQueue继承自Queue接口。

BlockingQueue接口提供了3个添加元素方法:

  • add:添加元素到队列里,添加成功返回true,由于容量满了添加失败会抛出IllegalStateException异常;
  • offer:添加元素到队列里,添加成功返回true,添加失败返回false;
  • put:添加元素到队列里,如果容量满了会阻塞直到容量不满。

3个删除方法:

  • poll:删除队列头部元素,如果队列为空,返回null。否则返回元素;
  • remove:基于对象找到对应的元素,并删除。删除成功返回true,否则返回false;
  • take:删除队列头部元素,如果队列为空,一直阻塞到队列有元素并删除。

常用的阻塞队列具体类有ArrayBlockingQueue、LinkedBlockingQueue、PriorityBlockingQueue、LinkedBlockingDeque等。

本文以ArrayBlockingQueue和LinkedBlockingQueue为例,分析它们的实现原理。

ArrayBlockingQueue

ArrayBlockingQueue的原理就是使用一个可重入锁和这个锁生成的两个条件对象进行并发控制(classic two-condition algorithm)。

ArrayBlockingQueue是一个带有长度的阻塞队列,初始化的时候必须要指定队列长度,且指定长度之后不允许进行修改。

它带有的属性如下:


  1. // 存储队列元素的数组,是个循环数组 
  2.  
  3. final Object[] items; 
  4.  
  5.   
  6.  
  7. // 拿数据的索引,用于take,poll,peek,remove方法 
  8.  
  9. int takeIndex; 
  10.  
  11.   
  12.  
  13. // 放数据的索引,用于put,offer,add方法 
  14.  
  15. int putIndex; 
  16.  
  17.   
  18.  
  19. // 元素个数 
  20.  
  21. int count; 
  22.  
  23.   
  24.  
  25. // 可重入锁 
  26.  
  27. final ReentrantLock lock; 
  28.  
  29. // notEmpty条件对象,由lock创建 
  30.  
  31. private final Condition notEmpty; 
  32.  
  33. // notFull条件对象,由lock创建 
  34.  
  35. private final Condition notFull;  

数据的添加

ArrayBlockingQueue有不同的几个数据添加方法,add、offer、put方法。

add方法:


  1. public boolean add(E e) { 
  2.  
  3.     if (offer(e)) 
  4.  
  5.         return true; 
  6.  
  7.     else 
  8.  
  9.         throw new IllegalStateException("Queue full"); 
  10.  
  11. }  

add方法内部调用offer方法如下:


  1. public boolean offer(E e) { 
  2.  
  3.     checkNotNull(e); // 不允许元素为空 
  4.  
  5.     final ReentrantLock lock = this.lock; 
  6.  
  7.     lock.lock(); // 加锁,保证调用offer方法的时候只有1个线程 
  8.  
  9.     try { 
  10.  
  11.         if (count == items.length) // 如果队列已满 
  12.  
  13.             return false; // 直接返回false,添加失败 
  14.  
  15.         else { 
  16.  
  17.             insert(e); // 数组没满的话调用insert方法 
  18.  
  19.             return true; // 返回true,添加成功 
  20.  
  21.         } 
  22.  
  23.     } finally { 
  24.  
  25.         lock.unlock(); // 释放锁,让其他线程可以调用offer方法 
  26.  
  27.     } 
  28.  
  29. }  

insert方法如下:


  1. private void insert(E x) { 
  2.  
  3.     items[putIndex] = x; // 元素添加到数组里 
  4.  
  5.     putIndex = inc(putIndex); // 放数据索引+1,当索引满了变成0 
  6.  
  7.     ++count; // 元素个数+1 
  8.  
  9.     notEmpty.signal(); // 使用条件对象notEmpty通知,比如使用take方法的时候队列里没有数据,被阻塞。这个时候队列insert了一条数据,需要调用signal进行通知 
  10.  
  11. }  

put方法:


  1. public void put(E e) throws InterruptedException { 
  2.  
  3.     checkNotNull(e); // 不允许元素为空 
  4.  
  5.     final ReentrantLock lock = this.lock; 
  6.  
  7.     lock.lockInterruptibly(); // 加锁,保证调用put方法的时候只有1个线程 
  8.  
  9.     try { 
  10.  
  11.         while (count == items.length) // 如果队列满了,阻塞当前线程,并加入到条件对象notFull的等待队列里 
  12.  
  13.             notFull.await(); // 线程阻塞并被挂起,同时释放锁 
  14.  
  15.         insert(e); // 调用insert方法 
  16.  
  17.     } finally { 
  18.  
  19.         lock.unlock(); // 释放锁,让其他线程可以调用put方法 
  20.  
  21.     } 
  22.  
  23. }  

ArrayBlockingQueue的添加数据方法有add,put,offer这3个方法,总结如下:

add方法内部调用offer方法,如果队列满了,抛出IllegalStateException异常,否则返回true

offer方法如果队列满了,返回false,否则返回true

add方法和offer方法不会阻塞线程,put方法如果队列满了会阻塞线程,直到有线程消费了队列里的数据才有可能被唤醒。

这3个方法内部都会使用可重入锁保证原子性。

数据的删除

ArrayBlockingQueue有不同的几个数据删除方法,poll、take、remove方法。

poll方法:


  1. public E poll() { 
  2.  
  3.     final ReentrantLock lock = this.lock; 
  4.  
  5.     lock.lock(); // 加锁,保证调用poll方法的时候只有1个线程 
  6.  
  7.     try { 
  8.  
  9.         return (count == 0) ? null : extract(); // 如果队列里没元素了,返回null,否则调用extract方法 
  10.  
  11.     } finally { 
  12.  
  13.         lock.unlock(); // 释放锁,让其他线程可以调用poll方法 
  14.  
  15.     } 
  16.  
  17. }  

poll方法内部调用extract方法:


  1. private E extract() { 
  2.  
  3.     final Object[] items = this.items; 
  4.  
  5.     E x = this.<E>cast(items[takeIndex]); // 得到取索引位置上的元素 
  6.  
  7.     items[takeIndex] = null; // 对应取索引上的数据清空 
  8.  
  9.     takeIndex = inc(takeIndex); // 取数据索引+1,当索引满了变成0 
  10.  
  11.     --count; // 元素个数-1 
  12.  
  13.     notFull.signal(); // 使用条件对象notFull通知,比如使用put方法放数据的时候队列已满,被阻塞。这个时候消费了一条数据,队列没满了,就需要调用signal进行通知 
  14.  
  15.     return x; // 返回元素 
  16.  
  17. }  

take方法:


  1. public E take() throws InterruptedException { 
  2.  
  3.     final ReentrantLock lock = this.lock; 
  4.  
  5.     lock.lockInterruptibly(); // 加锁,保证调用take方法的时候只有1个线程 
  6.  
  7.     try { 
  8.  
  9.         while (count == 0) // 如果队列空,阻塞当前线程,并加入到条件对象notEmpty的等待队列里 
  10.  
  11.             notEmpty.await(); // 线程阻塞并被挂起,同时释放锁 
  12.  
  13.         return extract(); // 调用extract方法 
  14.  
  15.     } finally { 
  16.  
  17.         lock.unlock(); // 释放锁,让其他线程可以调用take方法 
  18.  
  19.     } 
  20.  
  21. }  

remove方法:


  1. public boolean remove(Object o) { 
  2.  
  3.     if (o == null) return false; 
  4.  
  5.     final Object[] items = this.items; 
  6.  
  7.     final ReentrantLock lock = this.lock; 
  8.  
  9.     lock.lock(); // 加锁,保证调用remove方法的时候只有1个线程 
  10.  
  11.     try { 
  12.  
  13.         for (int i = takeIndex, k = count; k > 0; i = inc(i), k--) { // 遍历元素 
  14.  
  15.             if (o.equals(items[i])) { // 两个对象相等的话 
  16.  
  17.                 removeAt(i); // 调用removeAt方法 
  18.  
  19.                 return true; // 删除成功,返回true 
  20.  
  21.             } 
  22.  
  23.         } 
  24.  
  25.         return false; // 删除成功,返回false 
  26.  
  27.     } finally { 
  28.  
  29.         lock.unlock(); // 释放锁,让其他线程可以调用remove方法 
  30.  
  31.     } 
  32.  
  33. }  

removeAt方法:


  1. void removeAt(int i) { 
  2.  
  3.     final Object[] items = this.items; 
  4.  
  5.     if (i == takeIndex) { // 如果要删除数据的索引是取索引位置,直接删除取索引位置上的数据,然后取索引+1即可 
  6.  
  7.         items[takeIndex] = null; 
  8.  
  9.         takeIndex = inc(takeIndex); 
  10.  
  11.     } else { // 如果要删除数据的索引不是取索引位置,移动元素元素,更新取索引和放索引的值 
  12.  
  13.         for (;;) { 
  14.  
  15.             int nexti = inc(i); 
  16.  
  17.             if (nexti != putIndex) { 
  18.  
  19.                 items[i] = items[nexti]; 
  20.  
  21.                 i = nexti; 
  22.  
  23.             } else { 
  24.  
  25.                 items[i] = null; 
  26.  
  27.                 putIndex = i; 
  28.  
  29.                 break; 
  30.  
  31.             } 
  32.  
  33.         } 
  34.  
  35.     } 
  36.  
  37.     --count; // 元素个数-1 
  38.  
  39.     notFull.signal(); // 使用条件对象notFull通知,比如使用put方法放数据的时候队列已满,被阻塞。这个时候消费了一条数据,队列没满了,就需要调用signal进行通知  
  40.  
  41. }  

ArrayBlockingQueue的删除数据方法有poll,take,remove这3个方法,总结如下:

poll方法对于队列为空的情况,返回null,否则返回队列头部元素。

remove方法取的元素是基于对象的下标值,删除成功返回true,否则返回false。

poll方法和remove方法不会阻塞线程。

take方法对于队列为空的情况,会阻塞并挂起当前线程,直到有数据加入到队列中。

这3个方法内部都会调用notFull.signal方法通知正在等待队列满情况下的阻塞线程。

LinkedBlockingQueue

LinkedBlockingQueue是一个使用链表完成队列操作的阻塞队列。链表是单向链表,而不是双向链表。

内部使用放锁和拿锁,这两个锁实现阻塞(“two lock queue” algorithm)。

它带有的属性如下:


  1. // 容量大小 
  2.  
  3. private final int capacity; 
  4.  
  5.   
  6.  
  7. // 元素个数,因为有2个锁,存在竞态条件,使用AtomicInteger 
  8.  
  9. private final AtomicInteger count = new AtomicInteger(0); 
  10.  
  11.   
  12.  
  13. // 头结点 
  14.  
  15. private transient Node<E> head; 
  16.  
  17.   
  18.  
  19. // 尾节点 
  20.  
  21. private transient Node<E> last; 
  22.  
  23.   
  24.  
  25. // 拿锁 
  26.  
  27. private final ReentrantLock takeLock = new ReentrantLock(); 
  28.  
  29.   
  30.  
  31. // 拿锁的条件对象 
  32.  
  33. private final Condition notEmpty = takeLock.newCondition(); 
  34.  
  35.   
  36.  
  37. // 放锁 
  38.  
  39. private final ReentrantLock putLock = new ReentrantLock(); 
  40.  
  41.   
  42.  
  43. // 放锁的条件对象 
  44.  
  45. private final Condition notFull = putLock.newCondition();  

ArrayBlockingQueue只有1个锁,添加数据和删除数据的时候只能有1个被执行,不允许并行执行。

而LinkedBlockingQueue有2个锁,放锁和拿锁,添加数据和删除数据是可以并行进行的,当然添加数据和删除数据的时候只能有1个线程各自执行。

数据的添加

LinkedBlockingQueue有不同的几个数据添加方法,add、offer、put方法。

add方法内部调用offer方法:


  1. public boolean offer(E e) { 
  2.  
  3.     if (e == null) throw new NullPointerException(); // 不允许空元素 
  4.  
  5.     final AtomicInteger count = this.count; 
  6.  
  7.     if (count.get() == capacity) // 如果容量满了,返回false 
  8.  
  9.         return false; 
  10.  
  11.     int c = -1; 
  12.  
  13.     Node<E> node = new Node(e); // 容量没满,以新元素构造节点 
  14.  
  15.     final ReentrantLock putLock = this.putLock; 
  16.  
  17.     putLock.lock(); // 放锁加锁,保证调用offer方法的时候只有1个线程 
  18.  
  19.     try { 
  20.  
  21.         if (count.get() < capacity) { // 再次判断容量是否已满,因为可能拿锁在进行消费数据,没满的话继续执行 
  22.  
  23.             enqueue(node); // 节点添加到链表尾部 
  24.  
  25.             c = count.getAndIncrement(); // 元素个数+1 
  26.  
  27.             if (c + 1 < capacity) // 如果容量还没满 
  28.  
  29.                 notFull.signal(); // 在放锁的条件对象notFull上唤醒正在等待的线程,表示可以再次往队列里面加数据了,队列还没满 
  30.  
  31.         } 
  32.  
  33.     } finally { 
  34.  
  35.         putLock.unlock(); // 释放放锁,让其他线程可以调用offer方法 
  36.  
  37.     } 
  38.  
  39.     if (c == 0) // 由于存在放锁和拿锁,这里可能拿锁一直在消费数据,count会变化。这里的if条件表示如果队列中还有1条数据 
  40.  
  41.         signalNotEmpty(); // 在拿锁的条件对象notEmpty上唤醒正在等待的1个线程,表示队列里还有1条数据,可以进行消费 
  42.  
  43.     return c >= 0; // 添加成功返回true,否则返回false 
  44.  
  45. }  

put方法:


  1. public void put(E e) throws InterruptedException { 
  2.  
  3.     if (e == null) throw new NullPointerException(); // 不允许空元素 
  4.  
  5.     int c = -1; 
  6.  
  7.     Node<E> node = new Node(e); // 以新元素构造节点 
  8.  
  9.     final ReentrantLock putLock = this.putLock; 
  10.  
  11.     final AtomicInteger count = this.count; 
  12.  
  13.     putLock.lockInterruptibly(); // 放锁加锁,保证调用put方法的时候只有1个线程 
  14.  
  15.     try { 
  16.  
  17.         while (count.get() == capacity) { // 如果容量满了 
  18.  
  19.             notFull.await(); // 阻塞并挂起当前线程 
  20.  
  21.         } 
  22.  
  23.         enqueue(node); // 节点添加到链表尾部 
  24.  
  25.         c = count.getAndIncrement(); // 元素个数+1 
  26.  
  27.         if (c + 1 < capacity) // 如果容量还没满 
  28.  
  29.             notFull.signal(); // 在放锁的条件对象notFull上唤醒正在等待的线程,表示可以再次往队列里面加数据了,队列还没满 
  30.  
  31.     } finally { 
  32.  
  33.         putLock.unlock(); // 释放放锁,让其他线程可以调用put方法 
  34.  
  35.     } 
  36.  
  37.     if (c == 0) // 由于存在放锁和拿锁,这里可能拿锁一直在消费数据,count会变化。这里的if条件表示如果队列中还有1条数据 
  38.  
  39.         signalNotEmpty(); // 在拿锁的条件对象notEmpty上唤醒正在等待的1个线程,表示队列里还有1条数据,可以进行消费 
  40.  
  41. }  

LinkedBlockingQueue的添加数据方法add,put,offer跟ArrayBlockingQueue一样,不同的是它们的底层实现不一样。

ArrayBlockingQueue中放入数据阻塞的时候,需要消费数据才能唤醒。

而LinkedBlockingQueue中放入数据阻塞的时候,因为它内部有2个锁,可以并行执行放入数据和消费数据,不仅在消费数据的时候进行唤醒插入阻塞的线程,同时在插入的时候如果容量还没满,也会唤醒插入阻塞的线程。

数据的删除

LinkedBlockingQueue有不同的几个数据删除方法,poll、take、remove方法。

poll方法:


  1. public E poll() { 
  2.  
  3.     final AtomicInteger count = this.count; 
  4.  
  5.     if (count.get() == 0) // 如果元素个数为0 
  6.  
  7.         return null; // 返回null 
  8.  
  9.     E x = null; 
  10.  
  11.     int c = -1; 
  12.  
  13.     final ReentrantLock takeLock = this.takeLock; 
  14.  
  15.     takeLock.lock(); // 拿锁加锁,保证调用poll方法的时候只有1个线程 
  16.  
  17.     try { 
  18.  
  19.         if (count.get() > 0) { // 判断队列里是否还有数据 
  20.  
  21.             x = dequeue(); // 删除头结点 
  22.  
  23.             c = count.getAndDecrement(); // 元素个数-1 
  24.  
  25.             if (c > 1) // 如果队列里还有元素 
  26.  
  27.                 notEmpty.signal(); // 在拿锁的条件对象notEmpty上唤醒正在等待的线程,表示队列里还有数据,可以再次消费 
  28.  
  29.         } 
  30.  
  31.     } finally { 
  32.  
  33.         takeLock.unlock(); // 释放拿锁,让其他线程可以调用poll方法 
  34.  
  35.     } 
  36.  
  37.     if (c == capacity) // 由于存在放锁和拿锁,这里可能放锁一直在添加数据,count会变化。这里的if条件表示如果队列中还可以再插入数据 
  38.  
  39.         signalNotFull(); // 在放锁的条件对象notFull上唤醒正在等待的1个线程,表示队列里还能再次添加数据 
  40.  
  41.                 return x; 
  42.  

take方法:


  1. public E take() throws InterruptedException { 
  2.  
  3.     E x; 
  4.  
  5.     int c = -1; 
  6.  
  7.     final AtomicInteger count = this.count; 
  8.  
  9.     final ReentrantLock takeLock = this.takeLock; 
  10.  
  11.     takeLock.lockInterruptibly(); // 拿锁加锁,保证调用take方法的时候只有1个线程 
  12.  
  13.     try { 
  14.  
  15.         while (count.get() == 0) { // 如果队列里已经没有元素了 
  16.  
  17.             notEmpty.await(); // 阻塞并挂起当前线程 
  18.  
  19.         } 
  20.  
  21.         x = dequeue(); // 删除头结点 
  22.  
  23.         c = count.getAndDecrement(); // 元素个数-1 
  24.  
  25.         if (c > 1) // 如果队列里还有元素 
  26.  
  27.             notEmpty.signal(); // 在拿锁的条件对象notEmpty上唤醒正在等待的线程,表示队列里还有数据,可以再次消费 
  28.  
  29.     } finally { 
  30.  
  31.         takeLock.unlock(); // 释放拿锁,让其他线程可以调用take方法 
  32.  
  33.     } 
  34.  
  35.     if (c == capacity) // 由于存在放锁和拿锁,这里可能放锁一直在添加数据,count会变化。这里的if条件表示如果队列中还可以再插入数据 
  36.  
  37.         signalNotFull(); // 在放锁的条件对象notFull上唤醒正在等待的1个线程,表示队列里还能再次添加数据 
  38.  
  39.     return x; 
  40.  

remove方法:


  1. public boolean remove(Object o) { 
  2.  
  3.     if (o == null) return false; 
  4.  
  5.     fullyLock(); // remove操作要移动的位置不固定,2个锁都需要加锁 
  6.  
  7.     try { 
  8.  
  9.         for (Node<E> trail = head, p = trail.next; // 从链表头结点开始遍历 
  10.  
  11.              p != null; 
  12.  
  13.              trail = p, p = p.next) { 
  14.  
  15.             if (o.equals(p.item)) { // 判断是否找到对象 
  16.  
  17.                 unlink(p, trail); // 修改节点的链接信息,同时调用notFull的signal方法 
  18.  
  19.                 return true; 
  20.  
  21.             } 
  22.  
  23.         } 
  24.  
  25.         return false; 
  26.  
  27.     } finally { 
  28.  
  29.         fullyUnlock(); // 2个锁解锁 
  30.  
  31.     } 
  32.  

LinkedBlockingQueue的take方法对于没数据的情况下会阻塞,poll方法删除链表头结点,remove方法删除指定的对象。

需要注意的是remove方法由于要删除的数据的位置不确定,需要2个锁同时加锁。

作者:佚名

来源:51CTO

时间: 2024-08-02 20:01:24

Java阻塞队列实现原理分析的相关文章

java中HashMap的原理分析_java

我们先来看这样的一道面试题: 在 HashMap 中存放的一系列键值对,其中键为某个我们自定义的类型.放入 HashMap 后,我们在外部把某一个 key 的属性进行更改,然后我们再用这个 key 从 HashMap 里取出元素,这时候 HashMap 会返回什么? 文中已给出示例代码与答案,但关于HashMap的原理没有做出解释. 1. 特性 我们可以用任何类作为HashMap的key,但是对于这些类应该有什么限制条件呢?且看下面的代码: public class Person { priva

Java 框架 Netty 实现原理分析

文将主要分析Netty实现方面的东西,由于精力有限,本人并没有对其源码做了极细致的研 究.如果下面的内容有错误或不严谨的地方,也请指正和谅解.对于Netty使用者来说,Netty提供了几个典型的example,并有详尽的API doc和guide doc,本文的一些内容及图示也来自于Netty的文档,特此致谢. 1.总体结构 先放上一张漂亮的Netty总体结构图,下面的内容也主要围绕该图上的一些核心功能做分析,但对如Container Integration及Security Support等高

Java中的ConcurrentHashMap原理分析节选

集合是编程中最常用的数据结构.而谈到并发,几乎总是离不开集合这类高级数据结构的支持.比如两个线程需要同时访问一个中间临界区Queue,比如常会用缓存作为外部文件的副本HashMap.这篇文章主要分析jdk1.5的3种并发集合类型concurrent,copyonright,queue中的ConcurrentHashMap,让我们从原理上细致的了解它们,能够让我们在深度项目开发中获益非浅.      在tiger之前我们使用得最多的数据结构之一就是HashMap和Hashtable.大HashMa

Java实现单向双向链表原理分析

何为链表 链式结构是一种使用对象引用变量来创建对象间的链的数据结构. 对象引用变量可用于创建链式结构 对象引用变量所存储的特定地址一般无关紧要.换句话说,重要的是能够使用引用变量来访问对象,而对象在内存中的特定存储位置并不重要.因此,我们一般将引用变量描述为一个"指向"对象的名称,而不是显示其地址,按照这种理解,引用变量有时称为"指针" 一个指向对象的对象引用变量 下面的这个类就是一个链式结构: 123456 public class Person { privat

深入掌握Java技术EJB调用原理分析二

Home接口的Weblogic实现类的stub类 ((Hello Bean))_HomeImpl_WLStub(部署的时候动态生成字节码) Home接口的Weblogic实现类的skeleton类 ((Hello Bean))_HomeImpl_WLSkeleton(部署的时候动态生成字节码) Remote接口:Hello (用户编写) Remote接口的Weblogic实现类 ((Hello Bean))_EOImpl(EJBC生成) Remote接口的Weblogic实现类的stub类 ((

深入掌握Java技术EJB调用原理分析一

一个远程对象至少要包括4个class文件:远程对象:远程对象的接口:实现远程接口的对象的stub:对象的skeleton这4个class文件. 在EJB中则至少要包括10个class: Bean类,特定App Server的Bean实现类,Bean的remote接口,特定App Server的remote接口实现类,特定App Server的remote接口的实现类的stub类和skeleton类. Bean的home接口,特定App Server的home接口实现类,特定App Server的

剖析Java中阻塞队列的实现原理及应用场景_java

我们平时使用的一些常见队列都是非阻塞队列,比如PriorityQueue.LinkedList(LinkedList是双向链表,它实现了Dequeue接口). 使用非阻塞队列的时候有一个很大问题就是:它不会对当前线程产生阻塞,那么在面对类似消费者-生产者的模型时,就必须额外地实现同步策略以及线程间唤醒策略,这个实现起来就非常麻烦.但是有了阻塞队列就不一样了,它会对当前线程产生阻塞,比如一个线程从一个空的阻塞队列中取元素,此时线程会被阻塞直到阻塞队列中有了元素.当队列中有元素后,被阻塞的线程会自动

Java并发编程:阻塞队列

我们讨论了同步容器(Hashtable.Vector),也讨论了并发容器(ConcurrentHashMap.CopyOnWriteArrayList),这些工具都为我们编写多线程程序提供了很大的方便.今天我们来讨论另外一类容器:阻塞队列. 在前面我们接触的队列都是非阻塞队列,比如PriorityQueue.LinkedList(LinkedList是双向链表,它实现了Dequeue接口). 使用非阻塞队列的时候有一个很大问题就是:它不会对当前线程产生阻塞,那么在面对类似消费者-生产者的模型时,

深入理解Java线程编程中的阻塞队列容器_基础知识

1. 什么是阻塞队列? 阻塞队列(BlockingQueue)是一个支持两个附加操作的队列.这两个附加的操作是:在队列为空时,获取元素的线程会等待队列变为非空.当队列满时,存储元素的线程会等待队列可用.阻塞队列常用于生产者和消费者的场景,生产者是往队列里添加元素的线程,消费者是从队列里拿元素的线程.阻塞队列就是生产者存放元素的容器,而消费者也只从容器里拿元素. 阻塞队列提供了四种处理方法: 抛出异常:是指当阻塞队列满时候,再往队列里插入元素,会抛出IllegalStateException("Q