8 阻塞队列

目录

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

8 阻塞队列_第1张图片

2 阻塞队列

        如果队列满,往BlockingQueue中插入数据时,线程会阻塞直到队列非满;当阻塞队列空时,从BlockingQueue中取数据时,线程会阻塞直到队列非空

2.1 接口

8 阻塞队列_第2张图片

8 阻塞队列_第3张图片

2.2 应用场景

2.2.1 线程池

        线程池中的工作线程从任务队列中取出任务处理,如果任务队列为空,则工作线程会被阻塞直到任务队列非空

2.2.2 生产-消费者模型

        很好解决了生产消费者之间的并发问题

2.2.3 消息队列

2.2.4缓存系统

        有效避免并发更新缓存数据时的竞争和冲突

总结:阻塞队列可以帮我们解决并发问题,提高程序的性能和可靠性

2.2.5 并发任务处理

3 JUC包下的阻塞队列

8 阻塞队列_第4张图片

3.1 ArrayBlockingQueue

        有界阻塞队列,初始化时需要指定大小,利用ReentrantLock实现线程安全

        在生产消费模型中,生产者产出和消费者消费速度保持一致的情况下,使用ArrayBlockingQueue 是个不错的选择;若速度超过太多,则会造成大量阻塞

3.1.1 使用

3.1.2 原理

8 阻塞队列_第5张图片

        使用ReentrantLock独占锁实现线程安全,入队出队使用同一个锁对象,高并发场景下可能会出现瓶颈

3.1.3 数据结构

8 阻塞队列_第6张图片

3.1.4 入队方法

8 阻塞队列_第7张图片

8 阻塞队列_第8张图片

3.1.5 出队方法

8 阻塞队列_第9张图片

8 阻塞队列_第10张图片

为什么ArrayBlockingQueue使用双指针?

        可以避免数组的复制操作(删除元素之后,后面的元素前移);使得插入和删除元素的时间复杂度为O(1)

3.2 LinkedBlockingQueue

        基于链表的阻塞队列,理论上可以无限大,但若没有剩余内存,会触发OOM,所以初始化时通常会传一个队列大小

3.2.1 使用

8 阻塞队列_第11张图片

3.2.2 原理

        出队和入队采用两把锁,互不阻塞;LinkedBlockingQueue是读写分离的,读写操作并行执行

8 阻塞队列_第12张图片

3.2.3 数据结构

8 阻塞队列_第13张图片

3.2.4 入队方法

8 阻塞队列_第14张图片

3.2.5 出队方法

8 阻塞队列_第15张图片

 ArrayBlockingQueue和LinkedBlockingQueue的对比?

        1 队列大小不同

        2 数据存储容器不同;前者arr,后者链表

        3 前者不会产生垃圾对象,后者在高并发场景对GC可能存在较大的影响

        4 前者入队出队公用一把锁;后者入队出队是两把锁,并发性能更好

3.3 DelayQueue

        支持延迟获取元素的阻塞队列,元素必须实现Delayed接口

        延迟队列:不是先进先出,会按延迟时间的长短排序,下一个即将执行的任务会排到队列前面;无界队列,放的元素必须实现Delayed接口(继承Comparable)

3.3.1 使用

8 阻塞队列_第16张图片

3.3.2 原理

8 阻塞队列_第17张图片

3.3.3 数据结构

8 阻塞队列_第18张图片

3.3.4 入队方法

8 阻塞队列_第19张图片

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();
        }
    }

8 阻塞队列_第20张图片

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

你可能感兴趣的:(#,并发编程,java,开发语言)