Java并发包中并发队列
7.1ConcurrentLinkedQueue
线程安全的无界非阻塞队列,其底层数据结构使用单向链表实现,对于出队和入队操作使用CAS来实现线程安全
新元素会被插入队列的末尾
队列头获取元素
offer操作
添加一个元素
add操作
添加一个元素,底层使用offer()
poll操作
删除一个元素,获取元素
peek操作
获取队首的参数
remove操作
移除一个值
contains操作
不精确的判断
小结
ConcurrentLinkedQueue的底层使用单向链表数据结构来保存队列元素,每个元素被包装成一个Node节点。队列是靠头、尾节点来维护
创建队列头、尾节点指向一个item为null的哨兵节点
第一次执行peek或者first操作时会把head执行第一个真正的队列元素。
LinkedBlockingQueue原理探究
阻塞队列
有界
单向链表实现,两个Node用来存放队首尾节点
两个ReentrantLock实例,分别用来控制元素出队和入队,takeLock用来控制同时只有一个线程可以从队列头获取元素,
putLock控制同时只有一个线程可以在队列尾添加元素。
notEmpty和notFull:条件变量,用来存放出队和入队时被阻塞的线程。
Offer
向队列尾部插入一个元素,如果队列中有空闲则插入成功后返回true,如果队列满丢弃当前元素返回false-----非阻塞
put
如果空闲插入成功,如果已满,则阻塞当前进程,直到队列有空闲插入成功------------------阻塞的
poll
从队列头部获取并移除一个元素并返回,如果队列维空,则返回null,--------------------不阻塞
peek
从队列头部元素中返回,不移除,空则返回null---------------不阻塞
take
获取当前队列头部元素并从队列里面删除它,如果队列为空则阻塞当前线程---------------------阻塞的
remove
删除队列里面指定的元素,有则返回true
获取出锁和入锁
删除元素之后,唤醒notFull条件队列里面的线程,执行put方法
size操作
精确的,因为使用的AtomicInteger 存储count
为啥ConcurrentLinkedQueue中的size不精确,因为使用原子变量保证队列个数需要保证入队、出队操作和原子变量操作都是原子性的
小结
LinkedBlockingQueue的内部是通过单向链表实现的,使用头、尾节点来进行入队和出队操作,也就是入队操作都是队尾节点进行操作,出队操作都是队头节点进行操作
对头、为基点的操作分别使用了单独的独占锁从而保证原子性,所以出队和入队是可以同时进行的。另外对头、尾节点的独占锁都配备了一个条件队列,用来存放被阻塞的线程,并结合入队、出队操作实现了一个生产者消费者模型。
ArrayBlockingQueue原理探究
有界阻塞队列LinkedBlockingQueue
有界数组队列ArrayBlockingQueue
- 内部有一个items数组,用来存放队列元素
- putindex变量表示入队元素下标
- takeindex是出队小标
- count统计队列元素个数
- 这些变量都没有使用volatile修饰,这是因为这些变量都是在锁块内
- 用两个独占锁来保证出队、入队操作的原子性
- notEmpty、notFull条件变量用来进行出、入队的同步
offer
向队列尾部插入一个元素,如果队列有空闲空间插入成功后返回true,如果队列满,返回false,-----------------------------------------------不阻塞,独占锁
put
向队尾插入一个元素,空闲则插入后直接返回true,如果队列满了,则阻塞,直到true----------------------------------------阻塞,独占锁
poll
从队列头部获取一个元素,队列为空则返回null----------------------------不阻塞
peek
从队列头部获取一个元素,不移除
take
非要移除,不移除就阻塞
size
准确的
小结
ArrayBlockingQueue通过使用全局独占锁实现了一个同时只有一个线程进行入队和出队的操作,这个锁的粒度比较大,优点类是sychronized。其中offer和poll操作通过简单的加锁进行入队、出队操作。而put、take操作则使用条件变量实现的。相比于ArrayBlockingQueue的size操作的结果是精确的。
PriorityBlockingQueue
- 带优先级的无界阻塞队列
- 每次出队都返回优先级最高或者最低的元素
- 其内部使用平衡二叉树实现
- 内部有一个数组存放队列元素
- size存放队列元素个数
- allocationSpinLock是一个自旋锁,其实用CAS操作来保证同时只有一个线程可以扩容队列。
- lock独占锁对象用来控制同时只有一个线程可以入队和出队
- notEmpty用来存放take方法的线程
offer
在队列中插入一个元素,独占锁
poll
获取队列内部堆树的根节点元素
put
调用offer
take
获取根节点元素,阻塞的
size
精确的,加锁的
小结
priorityBlockingQueue队列在内部使用二叉树堆维护元素优先级,使用数组作为元素存储的数据结构,会扩容。
在内部使用一个独占锁来控制同时只有一个线程进行入队和出队。另外使用notEmpty来确保take方法的阻塞线程
DelayQueue
队列里面的元素实现Delayed接口,由于每一个元素都有一个过期时间,所以要实现获知当前元素还剩下多少时间过期了的结构,内部使用优先级队列来实现
- 条件变量available
- 当一个线程调用队列的take方法变成leader线程后,它会嗲用条件变量available.awaitNanos(delay)等待delay时间,但是其它线程则会调用available.await()进行无限等待。leader线程延迟时间过期后,会退出take方法,并唤醒别的线程
offer
插入元素到队列
take
移除队列里面延迟时间过期的元素,没有则z阻塞
poll
获取并移除对头过期元素,没有则返回null
size
计算队列元素
小结
其内部使用PriorityQueue存放数据,使用ReentrantLock实现线程同步。另外队列里面的元素要实现Delayed接口,其中一个是获取当前元素到过期时间剩余时间的接口,在出队时判断元素是否过期,一个时元素之间的比较接口,因为这是一个优先级队列。