线程池里面最重要的还有个并发容器,即阻塞队列BlockingQueue。
BlockingQueue是阻塞队列的接口,提供了常见的几种方法:
方法 | 抛出异常 | 返回值 | 阻塞 | 超时(阻塞一段时间) |
---|---|---|---|---|
插入方法 | add | offer | put | offer(time) |
移除方法 | remove | poll | take | poll(time) |
检查方法 | element | peek | N/A | N/A |
BlockingQueue是不接受null值的,当add, put, offer一个null值,会抛出NullPointerException。当poll操作没有元素可以返回的时候,return的是null值。
- 抛出异常:当队列满时,如果再往队列里插入元素(add),会抛出IllegalStateException("Queuefull")异常。当队列空时,从队列里获取元素(remove)会抛出NoSuchElementException异常。
- 返回值:当往队列插入元素时,会返回元素是否插入(offer)成功,成功返回true。如果是移除方法(poll),则是从队列里取出一个元素,如果没有则返回null。
- 一直阻塞:当阻塞队列满时,如果生产者线程往队列里put元素,队列会一直阻塞生产者线程,直到队列可用或者响应中断退出。当队列空时,如果消费者线程从队列里take元素,队列会阻塞住消费者线程,直到队列不为空。
- 超时退出:当阻塞队列满时,如果生产者线程往队列里插入元素,队列会阻塞生产者线程一段时间,如果超过了指定的时间,生产者线程就会退出。
常见的阻塞队列:
ArrayBlockingQueue:一个由数组结构组成的有界阻塞队列。适合生产者和消费者并发较小的情况。
按照先进先出原则,要求设定初始大小;内部使用预先分配大小的数组作为其存储空间,put和take操作不会增加垃圾回收的负担。
问题在于其put和take操作使用的是同一个锁(显式锁),从而会导致锁的高竞争,导致更多的上线文切换。LinkedBlockingQueue:一个由链表结构组成的无界阻塞队列。适合生产者和消费者并发较大的情况。
按照先进先出原则,可以不设定初始大小,最大为Integer.Max_Value(2^31 - 1),优点是put和take分别采用了两个显式锁(putLock和takeLock)。
问题是其内部是通过链表实现,put和take会导致链表节点的动态创建和删除,增加垃圾回收的负担。PriorityBlockingQueue:一个支持优先级排序的无界阻塞队列。(PriorityQueue不是线程安全的)
默认情况下,按照自然顺序,要么实现compareTo()方法,指定构造参数Comparator。DelayQueue:一个使用优先级队列实现(内部基于PriorityQueue)的无界阻塞队列。
支持延时获取的元素的阻塞队列,元素必须要实现Delayed接口。适用场景:实现自己的缓存系统,订单到期,限时支付等等。
DelayQueue是一个无界阻塞队列,只有在延迟期满时才能从中提取元素。该队列的头部是延迟期满后保存时间最长的Delayed 元素。SynchronousQueue:一个不存储元素的阻塞队列。
每一个put操作都要等待一个take操作
SynchronousQueue在线程池里使用的时候,是使用缓存空间的,线程池提交不上去,则会创建新的工作线程来处理任务,所以必须限制max_pool_size。