LinkedBlockingQueue、ArrayBlockingQueue、SynchronousQueue、ConcurrentLinkedQueue、TransferQueue原理简介

一、jdk中队列特点

在jdk中有许多队列,其使用还是有一些难度的,因为涉及到了并发等概念。现在,我们列举一下队列的特点:

(1)并发情况下,不会有线程安全问题。
(2)队列都有元素。
(3)都有添加(生产者端使用)、获取(消费者端使用)功能。
(4)在多线程、高并发场景下使用。


二、jdk中队列有哪些

2.1、LinkedBlockingQueue

LinkedBlockingQueue是一个无界、有缓存的等待队列。

在SingleThreadPool(单个线程的线程池)、FixedThreadPool(固定线程数的线程池)中使用的队列都是LinkedBlockingQueue。

LinkedBlockingQueue是基于链表的阻塞队列,内部维持着一个数据缓冲队列(该队列由链表构成)。当生产者往队列中放入一个数据时,队列会从生产者手中获取数据,并缓存在队列内部,而生产者立即返回;只有当队列缓冲区达到“缓存容量最大值”时(LinkedBlockingQueue可以通过构造函数指定该值),才会阻塞生产者队列,直到消费者从队列中消费掉一份数据,生产者线程会被唤醒。对于消费者的处理,也基于同样的原理。

LinkedBlockingQueue之所以能够高效地处理并发数据,是因为其对于生产者端和消费者端分别采用了独立的ReentrantLock锁来控制数据同步,这也意味着:在高并发的情况下,生产者和消费者可以并行地操作队列中的数据,以此来提高整个队列的并发性能。这两个ReentrantLock锁为:takeLock和putLock,分别是在添加元素和取出元素时使用。

2.2、ArrayBlockingQueue

 ArrayListBlockingQueue是一个有界、有缓存的等待队列,其添加和获取操作使用同一个ReentrantLock。在阿里的开源框架RocketMq中,使用到了ArrayBlockingQueue队列。
         ArrayBlockingQueue是基于数组的阻塞队列,同LinkedBlockingQueue类似,内部维持着一个定长的数据缓冲队列(该队列由数组构成)。ArrayBlockingQueue内部还保存着两个整形变量,分别标识“队列的头部和尾部在数组中的位置”。
         ArrayBlockingQueue在生产者放入数据和消费者获取数据时,都是共用同一个锁对象,由此也意味着两者无法真正并行运行。这点尤其不同于LinkedBlockingQueue。按照实现原理来分析,ArrayBlockingQueue完全可以采用分离锁,从而实现生产者和消费者操作的完全并行运行。Doug Lea之所以没这样去做,也许是因为ArrayBlockingQueue的数据写入和获取操作已经足够轻巧,以至于引入独立的锁机制,除了给代码带来额外的复杂性外,在性能上完全得不到任何好处。 ArrayBlockingQueue和LinkedBlockingQueue还有一个明显的不同之处在于:在插入或删除元素时,前者不会产生或销毁任何额外的对象实例,而后者则会生成一个额外的Node对象。这在长时间内需要高效并发地处理大批量数据的系统中,对于GC的影响还是存在一定的区别。

2.3、SynchronousQueue

SynchronousQueue是一种无界、无缓冲的等待队列,最多只能有一个元素。在CachedThreadPool线程池中,使用到了SynchronousQueue。

由于SynchronousQueue本身的特性,在某次添加元素后,必须等待其他线程取走后,才能继续添加。可以认为,SynchronousQueue是一个缓存长度为1的阻塞队列。但是,其isEmpty()方法永远返回true,remainingCapacity() 方法永远返回0,remove()和removeAll() 方法永远返回false,iterator()方法永远返回空,peek()方法永远返回null。

        声明一个SynchronousQueue有两种不同的方式:公平模式和非公平模式。如果采用公平模式,那么SynchronousQueue会采用公平锁,使用TransferQueue先进先出方式,来阻塞多余的生产者和消费者,从而体系整体的公平策略。如果采用非公平模式(SynchronousQueue默认),那么SynchronousQueue采用非公平锁,使用TransferStack后进先出的方式,来管理多余的生产者和消费者。对于后一种模式,如果生产者和消费者的处理速度有差距,则容易出现饥渴的情况,即可能有某些生产者或消费者的数据永远都得不到处理。加锁是使用CAS方式替换。

SynchronousQueue的添加和获取方法都使用了transfer方法。

public boolean offer(E e) {
   if (e == null) throw new NullPointerException();
       return transferer.transfer(e, true, 0) != null;
}
public E take() throws InterruptedException {
      E e = transferer.transfer(null, false, 0);
      if (e != null)
          return e;
      Thread.interrupted();
      throw new InterruptedException();
}


2.4、ConcurrentLinkedQueue

ConcurrentLinkedQueue是一个基于链接节点的、无界的、非阻塞的、线程安全队列。

在netty的读取byteBuffer和获取Selector中,都使用了ConcurrentLinkedQueue队列。

ConcurrentLinkedQueue采用先进先出的规则对节点进行排序,从tail节点添加,从head节点获取,而且使用“wait-free”算法实现高并发支持。因为不用锁,所以效率更高。

具体详细原理,可以网上搜索,在此不再赘述。

2.5、LinkedTransferQueue

LinkedTransferQueue是一个由链表结构组成的、无界的、阻塞TransferQueue队列。相对于其他阻塞队列,LinkedTransferQueue多了tryTransfer和transfer方法。

LinkedTransferQueue是ConcurrentLinkedQueue、SynchronousQueue(公平模式下转交元素)、LinkedBlockingQueue(阻塞Queue的基本方法)的超集。而且LinkedTransferQueue更好用,因为它不仅仅综合了这几个类的功能,同时也提供了更高效的实现,其添加和获取都是用的xfer()方法

猜你喜欢

转载自blog.csdn.net/chinawangfei/article/details/123008267