8 阻塞队列

本文详细介绍了Java中的阻塞队列,包括ArrayBlockingQueue、LinkedBlockingQueue和DelayQueue的使用、原理和选择策略,重点讲解了它们在线程池、生产-消费者模型、消息队列和并发任务处理中的应用,以及如何根据功能、容量和性能需求来选择合适的队列。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

目录

1 Queue

2 阻塞队列

2.1 接口

2.2 应用场景

2.2.1 线程池

2.2.2 生产-消费者模型

2.2.3 消息队列

2.2.4缓存系统

2.2.5 并发任务处理

3 JUC包下的阻塞队列

3.1 ArrayBlockingQueue

3.1.1 使用

3.1.2 原理

3.1.3 数据结构

3.1.4 入队方法

3.1.5 出队方法

3.2 LinkedBlockingQueue

3.2.1 使用

3.2.2 原理

3.2.3 数据结构

3.2.4 入队方法

3.2.5 出队方法

3.3 DelayQueue

3.3.1 使用

3.3.2 原理

3.3.3 数据结构

3.3.4 入队方法

3.3.5 出队方法

4 如何选择合适的阻塞队列

4.1 功能

4.2 容量

4.3 能够扩容

4.4 内存结构

4.5 性能

5 线程池对阻塞队列的选择


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同理)选取的是延迟队列

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值