并发队列总结
首先并发队列按照有界和无界有以下划分
有界队列 | 无界队列 |
---|---|
ArrayBlockingQueue、LinkedBlockingQueue | PriorityBlockingQueue、DelayQueue、ConcurrentLinkedQueue、 |
ArrayBlockingQueue
ArrayBlockingQueue 是一个用数组实现的有界阻塞队列,此队列按照先进先出(FIFO)的原则对元素进行操作。
ArrayBlockingQueue 中有两个个重要的属性,可重入锁和Condition,可重入锁是独占式的锁,来保证对队列的访问都是线程安全的,阻塞操作,那么什么情况下该阻塞,什么情况下不阻塞,这个是由Condition来控制的。
ArrayBlockingQueue 是有界的队列,需要指定队列的大小,它不会像ArrayList那样自动扩容,ReentrantLock 有公平锁和非公平锁之分,因此需要指定,默认是非公平锁。
ArrayBlockingQueue 线程安全,和Vector不一样,Vector 中用的synchronized 关键字进行线程同步,ArrayBlockingQueue 中通过ReentrantLock来完成的。
ArrayBlockingQueue 是一种逻辑上的环形队列。 ArrayBlockingQueue 在入队和出队上都使用了同一个重入锁,因此入队和出队是不能并发执行的。
LinkedBlockingQueue
LinkedBlockingQueue 是一个用链表实现的有界阻塞队列,此队列按照先进先出(FIFO)的原则对元素进行操作。
LinkedBlockingQueue 和ArrayBlockingQueue 是差不多的,只是储存方式由数组转换为链表了而已,LinkedBlockingQueue 也是通过重入锁和Condition来对队列的操作访问进行控制。
在ArrayBlockingQueue中,入队和出队都是用的同一个可重入锁,而LinkedBlockingQueue 对于出队和入队使用了不同的可重入锁来控制,ArrayBlockingQueue 入队和出队是不能同时并发的,而在LinkedBlockingQueue 中出队和出队是可以同时并发执行的(锁不一样)。 正是如此,对于count(队列中元素的个数)使用了原子类AtomicInteger,来保证对count的操作具有原子性。
PriorityBlockingQueue
PriorityBlockingQueue 是一个支持优先级的无界阻塞队列,默认情况下元素采取自然顺序升序排列,也可以自定义类实现compareTo()方法来指定元素排序规则,需要注意的是不能保证同优先级元素的顺序。
PriorityBlockingQueuey 是通过二叉堆来实现的,也是通过一个可重入锁来控制入队和出队操作,保证线程安全。
PriorityBlockingQueue 是无界队列,不会“队满”,实际当到达队列最大值后(Integer.MAX_VALUE - 8),就抛oom异常了,因此这点在使用优先队列的时候,需要注意。 二叉堆使用的是数组来实现,对一个二叉树进行编号,然后按照顺序存放在数组中。
观察一下父子之间的编号,会发现如果节点在数组中的位置是i(i是节点在数组中的下标), 则i节点对应的子节点在数组中的位置分别是 2i + 1 和 2i + 2,同时i的父节点的位置为 (i-1)/2。
因此现在我们就把树存储到了数组中,同时通过这种规律可以很快找到每个节点的父亲和孩子节点。
DelayQueue
DelayQueue 内部通过组合PriorityQueue 来实现存储和维护元素顺序的,其存储元素必须实现Delayed 接口,通过实现Delayed 接口,可以获取到元素延迟时间,以及可以比较元素大小(Delayed 继承Comparable)
DelayQueue 通过一个可重入锁来控制元素的入队出队行为
DelayQueue 中leader 标识 用于减少线程的竞争,表示当前有其它线程正在获取队头元素。
PriorityQueue 只是负责存储数据以及维护元素的顺序,对于延迟时间取数据则是在DelayQueue 中进行判断控制的。
SynchronousQueue
SynchronousQueue 不是一个真正的队列,其主要功能不是存储元素,而且维护一个排队的线程清单,这些线程等待把元素加入或者移除队列。每一个线程的入队(出队)操作必须等待另一个线程的出队(入队)操作。
SynchronousQueue中的队列不是针对数据的,而是针对操作,也就是入队不一定就是入队数据,而是入队的操作,操作可以是put,也可以是take,put操作与take操作对应,可以互相匹配,put和put,take和take则是相同的操作(模式)。
SynchronousQueue 并没有使用锁来保证线程的安全,使用的是循环CAS方法。
SynchronousQueue有两种模式:
1、公平模式
所谓公平就是遵循先来先服务的原则,因此其内部使用了一个FIFO队列来实现其功能。
2、非公平模式
SynchronousQueue中的非公平模式是默认的模式,其内部使用栈来实现其功能,也就是后来的先服务,
LinkedTransferQueue
LinkedTransferQueue 和SynchronousQueue 其实基本是差不多的,两者都是无锁带阻塞功能的队列,SynchronousQueue 通过内部类Transferer 来实现公平和非公平队列
在LinkedTransferQueue 中没有公平与非公平的区分,LinkedTransferQueue 实现了TransferQueue接口,该接口定义的是带阻塞操作的操作,相比SynchronousQueue 中的Transferer 功能更丰富。
LinkedTransferQueue是基于链表的FIFO无界阻塞队列,它是JDK1.7才添加的阻塞队列,有4种操作模式:
1 | private static final int NOW = 0; // for untimed poll, tryTransfer |
NOW :如果在取数据的时候,如果没有数据,则直接返回,无需阻塞等待。
ASYNC:入队的操作都不会阻塞,也就是说,入队后线程会立即返回,不需要等到消费者线程来取数据。
SYNC :取数据的时候,如果没有数据,则会进行阻塞等待。
TIMED : 取数据的时候,如果没有数据,则会进行超时阻塞等待。
ConcurrentLinkedQueue
这个和前面阻塞队列不同是,阻塞队列使用锁实现线程的安全,而此队列使用CAS非阻塞算法实现线程安全,使用非阻塞队列一般性能比较好,但是前提是竞争不那么激烈,如果竞争比较激烈还是前面阻塞算法使用同步锁的方式相对来说性能比较好。
此队列是借助于Michael-Scott 非阻塞队列算法来实现的队列。基于链表的无限制长度的线程安全队列,此队列元素FIFO(先进先出)。这个队列在add(),remove(),poll()都用了cas来保证安全。具体可以参考源码看是如何保持的线程安全的。
在iterator()时,如果集合被改变,那么数据可能会不一致。
LinkedBlockingDeque
LinkedBlockingDeque是基于双向链表的双端有界阻塞队列,默认使用非公平ReentrantLock实现线程安全,默认队列最大长度都为Integer.MAX_VALUE;不允许null元素添加;双端队列可以用来实现 “窃取算法” ,两头都可以操作队列,相对于单端队列可以减少一半的竞争。
LinkedBlockingDeque 是基于链表的,因此分析它实际就是分析链表而已,这个和我们前面LinkedBlockingQueue实质是差不多的,只是这里是双端队列,可以两头操作队列(队尾也可以出队,队头也可以入队)。
ConcurrentLinkedDeque
- 基于链接节点的无界并发的双端队列。
- 并发插入,删除和访问操作安全执行。
- 因为这些deques的异步性质,确定当前的数量,元素需要遍历元素,因此可以报告,如果在遍历期间修改此集合,则结果不准确。
- 也是使用cas来保证线程安全,这个类不仅可以操控头部,也可以操控尾部。