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