今前面已经学习了主要的阻塞队列,今天对它们整体来一次梳理。
先看下所有阻塞队列类在类图中的位置和继承结构,常见阻塞队列如下图:
上图已经把前面总结的阻塞队列的继承体系梳理出来了,之前学习的时候总是发现每个类继承一个类又实现了一个接口,然后父类的接口又继承或者实现,看上去很复杂。通过这个总结类图看上去就很简单了。
上图中对每个类都进行了简单的总结,接下来详细说明他们的作用。
首先是最顶层接口Iterable,它有一个属性Iterator,用来迭代元素,Collection继承它,所以所有集合类都有属性Iterator,可以遍历元素。
接口Collection就不多做介绍了,是集合类的顶层接口,用来定义集合类一些基础方法比如size、add、remove、contaions、isEmpty等,这里也说明了所有的队列也是属于集合家族的。
第三层是AbstractCollection抽象类和Queue接口。
AbstractCollection实现了一些方法,由于到这一层还没有定义顶层存储数据的结构,所以是利用Iterator遍历元素来实现一些方法比如contains、toArray、remove、toString。
而Queue接口则是定义了一些队列应该有的特征方法,比如poll、offer、peek。
第四层是AbstractQueue抽象类与BlockingQueue接口。
抽象类AbstractQueue继承了抽象类AbstractCollection,通过总结图可以看到所有的阻塞队列都实现了AbstractQueue。主要作用是把集合的特征进行队列化,主要是把add方法调用offer方法实现,remove用poll实现,通过这样处理虽然所有队列都是集合,但是并不会影响队列的特征,AbstractQueue还通过peek方法实现了element方法。
BlockingQueue接口则是声明阻塞队列的特征方法,主要是take、put,还有offer和poll的延时方法。
Deque接口,它继承Queue,是队列中的特殊队列:双向队列,所以Deque中主要是把Queue的出队入队方法基础上扩展了First、Last结尾的方法,表示可以从头部、尾部进行入队或者出队操作。
BlockingDeque则是对Deque往阻塞方向的扩展,也就是声明了阻塞队列拥有的take、put,还有offer和poll的延时方法以及他们的First、Last结尾的方法。
TransferQueue继承BlockingQueue接口,主要是进行了transfer扩展,新声明了transfer、tryTransfer方法,transfer方法会一直阻塞直到添加的数据被消费者消费。
DelayQueue队列中存储的元素必须实现Delayed接口,他主要有一个getDelay方法,可以理解成元素还是多长时间才能被消费,可以利用他来实现延迟执行,定时任务等功能。
接下来对阻塞队列进行对比,这里对每个类不会进行详细的介绍,详情可以看前面几篇文章,这里只说明他们的特征方便与其他队列进行对比,主要特征如下:
ArrayBlockingQueue:底层实现数组、先入先出、有界队列,构造是需指定数组长度且不可变,ReentrantLock、Condition实现线程安全;
LinkedBlockingQueue:底层实现链表,先入先出、无界队列,ReentrantLock、Condition实现线程安全;
PriorityBlockingQueue:底层数组实现二叉堆,数组可变,所以是支持优先级无界阻塞队列,ReentrantLock、Condition实现线程安全;
DelayQueue:底层数据是PriorityQueue(无锁无阻塞无界优先级队列),ReentrantLock、Condition实现线程安全,保存元素必须实现Delayed接口,可以指定元素出队时间;
SynchronousQueue:没有容量,不管是take还是put进来的线程,如果没有匹配就阻塞,等待异类线程交换数据并唤醒,支持公平与非公平模式,无锁通过CAS实现;
LinkedTransferQueue:链表实现无界阻塞队列,put方法不阻塞,take方法先进可以占位置,后面的put会先给到它,transfer方法与SynchronousQueue的公平模式一样,无锁通过CAS实现;
LinkedBlockingDeque:双向链表、无界阻塞队列,可实现先入先出、先入后出、优先进出,ReentrantLock、Condition实现线程安全;
实际上除了ArrayBlockingQueue与LinkedBlockingQueue,其他几个队列都有比较特殊的意义,所以对比的意义不大。
ArrayBlockingQueue主要是需要初始化一个数组,如果要存储的元素很多则需要初始化的数组太大,那么太占内存,而LinkedBlockingQueue则不需要初始化一个存储数据的结构,保存一个元素创建一个节点,但是每个元素消耗的内存就更多。
通过体系结构可以看出来接口都是声明一些特征用的,而抽象类则是实现接口的一些通用方法,或者能实现的方法。
可以通过一个我自己编的流程来记住这几个阻塞队列:
首先是实现了一个阻塞队列ArrayBlockingQueue,但是我发现它总是需要初始化存储结构浪费内存,所以又实现了LinkedBlockingQueue,不用初始化占用内存,有一个存一个;
后面又发现我虽然有些元素是先保存的,但是我想优先消费一些元素,所以就实现了一个优先级队列PriorityBlockingQueue。但是优先级有时候并不够,因为优先的原因是先执行,但是却还没有到我想执行的时间,所以实现了延迟队列DelayQueue来满足;
虽然把数据放到了队列中但是我并不确定一定被消费了,因为只有消费了我才行确定下一步,所以有实现了同步队列SynchronousQueue。但是同步队列又限制的太死,所以实现了LinkedTransferQueue,put方法不再阻塞,而是特有的方法transfer才保证一定消费。
但是可能还有更加灵活的情况,有可能想最先被消费或者最后被消费,也有可能想消费前面的,或者消费最后面的,所以实现了LinkedBlockingDeque。