什么是阻塞队列
# 什么是阻塞队列
顾名思义,阻塞队列就是一组阻塞的队列,由于队列的特性是先进先出 , 如果加上阻塞这个特性的话,就会变成 队列为空时,出阻塞; 队列满时,进阻塞;
# 阻塞队列的作用
利用这种特性,我们在很多场景下都可以利用线程安全来优雅的解决我们业务自身的线程安全问题,比如可以方便的编写线程安全的生产消费模型。
生产者只需要往队列里面不停地添加元素,消费者只需要从队列里面取出他们,如下图所示:
左侧有三个生产者线程,它会将生产出来的结果放到中间的阻塞队列中。
右侧有三个消费者线程,它们将会从阻塞队列中取出它所需要的内容并进行处理。
因为,阻塞队列天然是线程安全的,所以生产者和消费者都可以是多线程的,不会发生线程安全问题。
既然,阻塞队列是线程安全的,那么我们就可以将开发重心从保持线程安全性,转移到具体的业务逻辑中,大大的降低了开发的难度和工作量。
与此同时,队列的使用还能够解耦业务逻辑。
比如一个银行转账的程序,那么生产者线程不需要关心具体的转账逻辑,只需要把转账任务,如账户和金额放到队列中就可以,而不需要关心银行这个类如何去实现具体的转账业务,而作为银行这个类来讲,它会去从队列中取出来将要执行的具体的任务,再去通过自己的各种方法来完成本次转账。
# 主要并发队列关系图
上图展示了,队列Queue主要的实现类,可以看出,Java提供线程安全的队列分别为阻塞队列和非阻塞队列两大类。
阻塞队列都是BlockingQueue的实现类:
- ArrayBlockingQueue
- LinkedBlockingQueue
- SynchronousQueue
- DelayQueue
- PriorityBlockingQueue
- LinkedTransferQueue
和非阻塞队列 ConcurrentLinkedQueue.
除此之外,还有双端队列Deque: 它从头和尾都能添加和删除元素;而普通的 Queue 只能从一端进入,另一端出去。
还有扩展了BlockingQueue的TransferQueue: 生产者会一直阻塞直到所添加到队列的元素被某一个消费者所消费(不仅仅是添加到队列里就完事)
后面我们会一一介绍他们。
# 阻塞队列的特点
阻塞队列区别于其他类型的队列的最主要的特点就是"阻塞"。
阻塞功能使得生产者和消费者两段的能力得以平衡,当任何一端的速度过快时,阻塞队列便会把过快的速度给降下来,阻塞最重要的两个方法是take方法和put方法。
take 方法
take 方法的功能是获取并移除队列的头结点,通常在队列里有数据的时候是可以正常移除的。可是一旦执行 take 方法的时候,队列里无数据,则阻塞,直到队列里有数据。
一旦队列里有数据了,就会立刻解除阻塞状态,并且取到数据。
put方法
put 方法插入元素时,如果队列没有满,那就和普通的插入一样是正常的插入,但是如果队列已满,那么就无法继续插入,则阻塞,直到队列里有了空闲空间。如果后续队列有了空闲空间,比如消费者消费了一个元素,那么此时队列就会解除阻塞状态,并把需要添加的数据添加到队列中。
以上过程中的阻塞和解除阻塞,都是 BlockingQueue 完成的,不需要我们自己处理。
# 是否有界(容量有多大)
此外,阻塞队列还有一个非常重要的属性,那就是容量的大小,分为有界和无界两种。
无界队列意味着里面可以容纳非常多的元素,例如 LinkedBlockingQueue 的上限是 Integer.MAX_VALUE,约为 2 的 31 次方,是非常大的一个数,可以近似认为是无限容量,因为我们几乎无法把这个容量装满。
但是有的阻塞队列是有界的,例如 ArrayBlockingQueue 如果容量满了,也不会扩容,所以一旦满了就无法再往里放数据了。