并发之 阻塞队列
本节内容总结自《Java 并发编程的艺术》
Java中的阻塞队列
什么是阻塞队列
-
定义:阻塞队列是支持阻塞的插入和移除方法。支持阻塞的插入是指,当队列满时,队列会阻塞插入元素的线程,直到队列不满。支持阻塞的移除方法是指,队列为空时,获取元素的线程会等待队列变为非空。阻塞队列常用于生产者和消费者的场景,生产者是向队列中添加元素的线程,消费者是从队列中取元素的线程。
-
阻塞队列中提供的处理方式
方法 抛出异常 返回特殊值 一直阻塞 超时退出 插入方法 add(e) offer(e) put(e) offer(e, time, unit) 移除方法 remove() poll() take() poll(time, unit) 检查方法 element() peek() 不可用 不可用
Java 中的阻塞队列
- ArrayBlockingQueue:是一个用数组实现的有界阻塞队列。按照插入顺序存储元素。默认情况下不保证线程公平的访问队列,为了保证公平性,可以在new时第二个参数使用true来创建一个公平性的队列。在该队列中,公平性是通过可重入锁ReentrantLock实现的。
- LinkedBlockingQueue:是一个用链表实现的有界阻塞队列。队列的默认和最大长度为Integer.MAX_VALUE。
- PriorityBlockingQueue:是一个支持优先级的无界阻塞队列。默认情况下采用升序排列。可以自定义类实现compareTo()方法来指定元素排序规则,或者初始化队列时,指定构造参数Comparator来对元素进行排序。
- DelayQueue:是一个支持延时获取元素的无界阻塞队列。队列使用PriorityQueue实现,队列中的元素必须实现Delayed接口,在创建元素时可以指定多久才能从队列中获取当前元素。只有在延迟期满时才能从队列中提取元素。
- DelayQueue的使用场景
- 缓存系统的设计:可以用DelayQueue保存缓存元素的有效期,使用一个线程循环查询DelayQueue,一旦能从队列中过获取到元素,表示缓存有效期已经到了。
- 定时任务调度:使用DelayQueue保存当天将会执行的任务和执行时间,一旦从DelayQueue中获取到任务就开始执行。
- DelayQueue的使用场景
- SynchronousQueue:是一个不存储元素的阻塞队列。每一个put操作必须等待一个take操作,否则不能继续添加元素。他支持公平访问队列(参数为true),默认情况下线程采用非公平性策略访问队列。SynchronousQueue可以看成是一个传球手,负责把生产者线程处理的数据直接传递给消费者线程。队列本身不存储任何元素,非常适合传递性场景。
- LinkedTransferQueue:是一个由链表结构组成的无界阻塞队列。相对于其他阻塞队列,LinkedTransferQueue队列多了transfer和tryTransfer方法。
- transfer方法:如果当前有消费者正在等待接收元素,transfer方法可以把生产者传入的元素立刻transfer给消费者。如果没有消费者在等待接收元素,transfer方法会将元素存放在队列的tail节点,并等待该元素被消费者消费了才返回。transfer方法会自旋等待消费者线程,但自旋一定次数之后也可以使用Thread.yield()方法来暂停自旋,并执行其他线程。
- tryTransfer方法:是用来试探生产者传入的元素是否能直接给消费者。如果没有消费者接收元素则返回false。和transfer方法的区别是,不论消费者是否存在,该方法都会立即返回。有时间限制的tryTransfer(E e, long timeout, TimeUnit unit)方法,试图把生产者传入的元素直接传给消费者,但是如果没有消费者消费该元素则等待指定时间再返回。
- LinkedBlockingDeque:是一个由链表结构组成的双向阻塞队列。它支持两端插入和删除元素。双向队列因为多了一个操作队列的入口和出口,在多线程同时入队时,也就减少了一般的竞争。
阻塞队列的实现分析
- 可以使用等待通知机制实现,就是当生产者往满的队列中插入元素时会阻塞住生产者,消费者消费了一个队列中的元素后会通知生产者停止阻塞。可以使用 Object类中的等待通知机制,或者 Condition实现。
时间并不会因为你的迷茫和迟疑而停留,就在你看这篇文章的同时,不知道有多少人在冥思苦想,在为算法废寝忘食,不知道有多少人在狂热地拍着代码,不知道又有多少提交一遍又一遍地刷新着OJ的status页面……
没有谁生来就是神牛,而千里之行,始于足下!