概述
- 消费者阻塞:在队列为空时·,消费者端的线程都会被自动阻塞(挂起),直到有数据放入队列,消费者线程会被自动唤醒并消费数据,如图所示
![](https://i-blog.csdnimg.cn/blog_migrate/e20b9a12434da54dcef19e0c39b55731.png)
- 生产者阻塞:在队列已满且没有可用空间时,生产者端的线程都会被自动阻塞(挂起),直到队列中有空的位置腾出,线程会被自动唤醒并生产数据,如图所示
![](https://i-blog.csdnimg.cn/blog_migrate/68684cff7d6d1e1f10a2b5967b0e8b26.png)
一、阻塞队列的主要操作
1.插入操作
- public abstract boolean add(E paramE): 将指定的元素插人队列中,在成功时返回 ture,如果当前没有可用的空间,则抛出IllegalStateException。如果该元素是null, 则抛出NullPointerException异常。Jdk源码的实现如下:
- public abstract boolean offer(E paramE): 将指定的元素插入队列中,在成功时返回true,如果当前没有可用的空间,则返回false。jdk源码的实现如下:
- offer(E o, long timeout, TimeUnit unit): 将指定的元素插入队列中,可以设定等待的时间,如果在设定的等待时间内仍不能向队列中加入元素, 则返回 false。
- public abstract void put(E p aramE) throws InterruptedException 将指定的元素插入队列中,如果队列已经满了, 则阻塞、等待可用的队列空间的释放,直到有可用的队列空间释放且插入成功为止,JDK 源码的实现如下
![](https://i-blog.csdnimg.cn/blog_migrate/a4c8c3bb2780429547b983d96e961b63.png)
2.获取数据操作
- poll()取走队列队首的对象,如果取不到数据,则返回null。jdk源码的实现如下:
![](https://i-blog.csdnimg.cn/blog_migrate/9b4d35f6904db7dc07f455b13f34194f.png)
- poll(long timeout, T imeUnit unit ):取走列队首的对象,如果在指定的时间内队列有数据可取,则返回队列中的数据,否则等待一定时间 ,在等待超时并且没有数据可取时,返回null。
- take () 取走列队首的对象,如果队列为空,则进入阻塞状态等待,知道队列有新的数据被加入,再及时取出新加入的数据。jdk源码的实现如下
- drainTo(Collection collection) 一次性从队列中批量获取所有可用的数据对象,同时可以指定获取数据的个数,通过该方法可以提升获取数据的效率, 避免多次频繁操 作引起的队列锁定。
二、Java中的阻塞队列实现
- ArrayBlockingQueue
![](https://i-blog.csdnimg.cn/blog_migrate/8352a751a5f7beeb5d94bc42024930ef.png)
- LinkedBlockingQueue
LinkedBlockingQueue是基于链表实现的阻塞队列,同ArrayBlockingQueue类似,此队列按照先进先出原则对元素进行排序,LinkedBlockingQueue对生产者端和消费者端分别采用两个独立的锁来控制数据同步,我们可以将队列头部的锁理解为写锁,尾部的锁理解成读锁,因此生产者和消费者可以基于各自独立的锁并行的操作队列中的数据,队列的并发性能较高,具体用法如下:
- PriorityBlockingQueue
PriorityBlockingQueue是一个支持优先级的无界队列。元素在默认情况下采用自然顺序升序排序。可以自定义实现compareTo方法来指定元素进行排序规则,或者在初始化PriorityBlockingQueue时指定构造参数Comparator来实现对元素的排序。注意:如果两个元素的优先级相同,则不能保证该元素的存储和访问顺序。具体用法如下:
- DelayQueue
DelayQueue是一个支持延时获取元素的无界阻塞队列,在队列底层使用PriorityQueue实现。DelayQueue队列中的元素必须实现Delayed接口,该接口定义了在创建元素时该元素的延迟时间,在内部通过为每个元素的操作加锁来保障数据的一致性。只有在延迟时间到后才能从队列中提取元素。我们可以将 DelayQueue运用于以下场景中
缓存系统的设计:可以用 DelayQueue保存缓存元素的有效期,使用一个线程循环查询DelayQueue一旦能从 DelayQueue 获取元素, 表示缓存的有效期到了。
定时任务调度:使用 DelayQueue 保存即将执行的任务和执行时间,一旦从 DelayQueue 中获取元素,就表示任务开始执行,Java 中的 TimerQueue 就是使用 DelayQueue 实现的。
在具体使用时,延迟对象必须先实现Delayed类并实现其getDelay方法和compareTo方法,才可以在延迟队列中使用:
- SynchronousQueue
SynchronousQueue时一个不存储元素的阻塞队列。SynchronousQueue中的每一个put操作都必须等待一个take操作完成,否则不能继续添加元素。我们可以将SynchronousQueue看作一个快递员,它负责把生产者线程的数据直接传递给消费线程,非常适用于传递型场景,比如将在一个线程中使用的数据传递给另一个线程使用。SynchronousQueue的吞吐量高于ArrayBlockingQueue 、LinkedBlockingQueue,具体的使用方法如下:
- LinkedTransferQueue
LinkedTransferQueue是基于链表结构实现的无界阻塞TransferQueue队列。相对于其他阻塞队列,LinkedTransferQueue多了个transfer、tryTransfer和tryTransfer(E e,long timeout,TimeUint unit)方法。
transfer方法:如果当前有消费者正在等待接收元素, transfer 方法就会直接把生产者传入的元素投递给消费者并返回 true。如果没有消费者在等待接收元素,transfer 方法就会将元素存放在队列的尾部( taiI )节点,直到该元素被消费后才返回。
tryTransfer方法:首先尝试能否将生产者传入的元素直接传给消费者,如果没有消费者等待接收元素,则返回 false。和transfer方法的区别是,无论消费者是否接收元素,tryTransfer方法都立即返回,而 transfer 方法必须等到元素被消费后才返回。
tryTransfer(E e,long timeout,TimeUint unit)方法:首先尝试生产者传入的元素直接传给消费者,如果没有消费者,则等待指定的时间,在超时后如果元素还没 有被消费,则返回 false ,否则返回true。
- LinkedBlockingDeque
LinkedBlockingDeque是基于链表结构实现的双向阻塞队列,可以在队列的两端分别执行插入和移出元素操作。这样,在多线程同时操作队列时,可以减少一半的锁资源竞争,提高队列的操作效率。