JDK 中提供了一系列场景的并发安全队列。总的来说,按照实现方式的不同可分为阻塞队列和非阻塞队列,前者使用锁实现,而后者则使用CAS 非阻塞算法实现。
1 非阻塞队列–ConcurrentLinkedQueue
ConcurrentLinkedQueue是无界非阻塞队列,内部使用单项链表实现;其中有两个volatile 类型的Node 节点分别用来存放队列的首、尾节点。从下面的无参构造函数可知,默认头、尾节点都是指向item为null的哨兵节点。
boolean casItem(E cmp, E val) {
return UNSAFE.compareAndSwapObject(this, itemOffset, cmp, val);
}
void lazySetNext(Node<E> val) {
UNSAFE.putOrderedObject(this, nextOffset, val);
}
boolean casNext(Node<E> cmp, Node<E> val) {
return UNSAFE.compareAndSwapObject(this, nextOffset, cmp, val);
}
private boolean casTail(Node<E> cmp, Node<E> val) {
return UNSAFE.compareAndSwapObject(this, tailOffset, cmp, val);
}
private boolean casHead(Node<E> cmp, Node<E> val) {
return UNSAFE.compareAndSwapObject(this, headOffset, cmp, val);
}
以上这些方法都是保证原子性操作而实现的。由于ConcurrentLinkedQueue是非阻塞队列,在添加和删除元素时不会进行加锁,所以对head和tail连个属性中上加上了volatile保证了内存可见性,加上cas保证了原子性,从而实现了线程安全。
2 阻塞队列–LinkedBlockingQueue
LinkedBlockingQueue 的内部是通过单向链表实现的,使用头、尾节点来进行入队和
出队操作,也就是入队操作都是对尾节点进行操作,出队操作都是对头节点进行操作。
对头、尾节点的操作分别使用了单独的独占锁从而保证了原子性,所以出队和入队操作是可以同时进行的。另外对头、尾节点的独占锁都配备了一个条件队列,用来存放被阻塞的线程,并结合入队、出队操作实现了一个生产消费模型。
3 阻塞队列–ArrayBlockingQueue
ArrayBlockingQueue 通过使用全局独占锁实现了同时只能有一个线程进行入队或者出队操作,这个锁的粒度比较大,有点类似于在方法上添加synchronized的意思。其中。他r 和poll 操作通过简单的加锁进行入队、出队操作,而put 、take 操作则使用条件变量实现了,如果队列满则等待,如果队列空则等待,然后分别在出队和入队操作中发送信号激活等待线程实现同步。另外,相比LinkedBlockingQueue,ArrayBlockingQueue 的size 操作的结果是精确的, 因为计算前加了全局锁。
4 阻塞队列–PriorityBlockingQueue
PriorityBlockingQueue 队列在内部使用二叉树堆维护元素优先级,使用数组作为元素
存储的数据结构,这个数组是可扩容的。当当前元素个数>=最大容量时会通过CAS 算法扩容,出队时始终保证出队的元素是堆树的根节点,而不是在队列里面停留时间最长的元素。使用元素的compareTo 方法提供默认的元素优先级比较规则,用户可以自定义优先级的比较规则。使用全局独占锁实现线程安全的。