目录
1 Queue
2 阻塞队列
如果队列满,往BlockingQueue中插入数据时,线程会阻塞直到队列非满;当阻塞队列空时,从BlockingQueue中取数据时,线程会阻塞直到队列非空
2.1 接口
2.2 应用场景
2.2.1 线程池
线程池中的工作线程从任务队列中取出任务处理,如果任务队列为空,则工作线程会被阻塞直到任务队列非空
2.2.2 生产-消费者模型
很好解决了生产消费者之间的并发问题
2.2.3 消息队列
2.2.4缓存系统
有效避免并发更新缓存数据时的竞争和冲突
总结:阻塞队列可以帮我们解决并发问题,提高程序的性能和可靠性
2.2.5 并发任务处理
3 JUC包下的阻塞队列
3.1 ArrayBlockingQueue
有界阻塞队列,初始化时需要指定大小,利用ReentrantLock实现线程安全
在生产消费模型中,生产者产出和消费者消费速度保持一致的情况下,使用ArrayBlockingQueue 是个不错的选择;若速度超过太多,则会造成大量阻塞
3.1.1 使用
3.1.2 原理
使用ReentrantLock独占锁实现线程安全,入队出队使用同一个锁对象,高并发场景下可能会出现瓶颈
3.1.3 数据结构
3.1.4 入队方法
3.1.5 出队方法
为什么ArrayBlockingQueue使用双指针?
可以避免数组的复制操作(删除元素之后,后面的元素前移);使得插入和删除元素的时间复杂度为O(1)
3.2 LinkedBlockingQueue
基于链表的阻塞队列,理论上可以无限大,但若没有剩余内存,会触发OOM,所以初始化时通常会传一个队列大小
3.2.1 使用
3.2.2 原理
出队和入队采用两把锁,互不阻塞;LinkedBlockingQueue是读写分离的,读写操作并行执行
3.2.3 数据结构
3.2.4 入队方法
3.2.5 出队方法
ArrayBlockingQueue和LinkedBlockingQueue的对比?
1 队列大小不同
2 数据存储容器不同;前者arr,后者链表
3 前者不会产生垃圾对象,后者在高并发场景对GC可能存在较大的影响
4 前者入队出队公用一把锁;后者入队出队是两把锁,并发性能更好
3.3 DelayQueue
支持延迟获取元素的阻塞队列,元素必须实现Delayed接口
延迟队列:不是先进先出,会按延迟时间的长短排序,下一个即将执行的任务会排到队列前面;无界队列,放的元素必须实现Delayed接口(继承Comparable)
3.3.1 使用
3.3.2 原理
3.3.3 数据结构
3.3.4 入队方法
3.3.5 出队方法
public E take() throws InterruptedException {
final ReentrantLock lock = this.lock;
lock.lockInterruptibly();
try {
for (;;) {
E first = q.peek();
if (first == null)
available.await();
else {
long delay = first.getDelay(NANOSECONDS);
if (delay <= 0)
return q.poll();
first = null; // don't retain ref while waiting
if (leader != null)
available.await();
else {
Thread thisThread = Thread.currentThread();
leader = thisThread;
try {
available.awaitNanos(delay);
} finally {
if (leader == thisThread)
leader = null;
}
}
}
}
} finally {
if (leader == null && q.peek() != null)
available.signal();
lock.unlock();
}
}
4 如何选择合适的阻塞队列
通常从以下几个方面考虑
4.1 功能
是否需要阻塞队列帮我们排序(优先级排序、延迟执行等),如果有需求则选用PriorityBlockingQueue、DelayBlockingQueue之类有排序能力的队列
4.2 容量
需要根据任务数量推算出容量
4.3 能够扩容
选择能动态扩容的队列,而非ArrayBlockingQueue
4.4 内存结构
数组/链表;想要空间利用率更高可以考虑数组结构实现的ArrayBlockingQueue
4.5 性能
LinkedBlockingQueue拥有两把锁,ArrayBlockingQueue只有一把锁,并发高的时候LinkedBlockingQueue >> ArrayBlockingQueue
5 线程池对阻塞队列的选择
Executors类下的线程池类型:
FixedThreadPool (SingleThreadExecutor同理)选取的是LinkedBlockingQueue
CachedThreadPool选取的是SynchronousQueue
ScheduledThreadPool (SingleThreadscheduledExecutor同理)选取的是延迟队列