1. ConcurrentLinkedQueue 无界非阻塞队列
ConcurrentLinkedQueue使用单向链表实现,对于出队和入队的操作使用CAS+自旋来实现线程安全。

类内部有两个volatile修饰的Node节点,用来存储头节点和尾节点。由于使用的是CAS无阻塞的算法所以入队和出队不会阻塞线程。入队和出队的时候会使用UnSafe的工具类,调用CAS进行Node节点的设置,如入队会将尾节点从null设置为新插入的Node引用,如果设置成功,则返回,如果不成功,说明有其他线程先行成功了CAS已经插入了一个节点,那么会进行循环自旋,直至设置成功。出队同理。相比阻塞算法,这个是CPU资源换取阻塞带来的开销。
因为使用了非阻塞算法,所以并发下size()的获取并不是很准确。

head和tail节点使用volatile修饰,保证了多线程情况下变量的可见性。
2. LinkedBlockingQueue 有界阻塞队列
LinkedBlockingQueue也是使用单向链表实现的,并且使用了两个独占锁ReentrantLock,来分离出队入队的锁定操作,同时使用两个条件变量来实现生产者消费者的阻塞唤醒。

LinkedBlockingQueue中有两个独占锁,takeLock和PutLock。
- takeLock用来控制只能由一个线程获取队头元素,其他线程必须等待。
- putLock用来控制同时只能有一个线程来向队尾添加元素。
- notEmpty和notFull是两个条件变量,每个条件变量有一个条件队列,用来存放入队和出队的阻塞线程。这其实是一个生产者消费者的模型。

- 对于入队操作,首先会获取putlock,获取成功后,会去检查是否队列已经满,如果满了,那么调用notFull的await方法,将当前线程阻塞,并加入阻塞队列。并使用while循环来一直检查是否队满,来避免虚假唤醒。如果没有满,那么会调用notEmpty的signal方法,来唤醒出队操作的阻塞线程。
- 对于出队操作,首先会获取takelock,获取成功后,回去检查是否队列已空,如果空了那么调用notEmpty的await方法,将当前线程阻塞,并加入阻塞队列。并使用while循环来一直检查是否队空,来避免虚假唤醒。如果不为空,那么会调用notFull的signal来唤醒入队操作的阻塞线程。
队头尾节点分别使用了独占锁,来保证原子性,所以出队入队可以同时进行,对两个独占锁配备了各自的条件队列,来存放阻塞线程,并结合出队入队操作,来实现了生产者消费者的模型。
3. ArrayBlockingQueue 有界阻塞队列
ArrayBlockingQueue使用有界数组的方式来实现队列。其中有一个独占锁Reentrantlock来保证入队出队操作的原子性,另外有两个notEmpty和notFull的条件变量,来实现入队出队操作的同步生产消费者模型。


ArrayBlockingQueue在入队出队的过程中加了全局独占锁,锁粒度比较大,和直接使用sychronized有点类似,另外相比于LinkdBlockingQueue,因为加了全局锁,所以ArrayBlokcingQueue的size()结果是准确的。
4. PriorityBlockingQueue 优先级无界阻塞队列
PriorityBlockingQueue是带有优先级的无界阻塞队列,每次出队都是优先级最高或者最低的元素出队,它的内部是使用平衡二叉堆来实现的,所以直接遍历的话不保证有序。

PriorityBlockingQueue里有一个数组queue用来存放大顶堆或者小顶堆,取决于比较算法。size用来存放元素个数。一个独占锁ReentrantLock,一个条件变量notEmpty。还有一个volatile修饰的int变量allocationSpinLock,用来用做扩容时的自旋锁。一个比较器comparator用来比较元素大小,实现优先级排列,可用户自定义规则。
- 入队:put操作首先会获取独占锁,来保证只能有一个线程出队入队,成功后会判断是否需扩容,也就是判断是否queue数组已满,若需要扩容,进入while循环,首先会释放独占锁,unlock掉。为什么?因为其实扩容时间较长,释放锁不影响出队操作,并且扩容使用的是CAS操作,首先会将allocationSpingLock使用unsafe工具CAS设置锁定,如果失败,那么说明已经有线程在进行扩容操作,那么会使用yeild方法让出cpu,让扩容操作尽快完成,并进行循环查看是否已经扩容完成。扩容完成后,会重新获取独占锁,来进行元素的平衡二叉堆的插入。这里把源代码贴上来。


- 出队:出队首先获取独占锁,然后检查是否为空,如果为空会调用条件变量来进行阻塞,并将线程封装成阻塞队列的Node加入队列,以此来进行,空队列的等待。如果不为空,那么会获取堆顶元素,来进行移除弹出,然后重新将堆调整为大顶堆或小顶堆。

PriorityBlockingQueue内部使用数组来维护一个平衡二叉堆,这个数组是可扩容的,当元素个数大于最大容量的时候,可以使用CAS算法来进行扩容,内部用一个独占锁来控制同时只有一个线程进行入队/出队一个操作,之所以只有一个notEmpty条件变量来进行阻塞没有notFull,是因为这是一个无界队列。
4. DelayQueue 无界阻塞延迟队列
DelayQueue是一个并发无界阻塞延迟队列,队列中每个元素都有个过期时间,当队列元素过期时才对出队。

类中有一个ReentrantLock独占锁来保证出队入队安全性,一个优先级队列PriorityQueue来存放元素,一个 Thread类型的leader属性,一个avaliable条件变量。因为使用优先级队列来实现,那么每一个元素都需要实现比较优先级的方法。
- 入队:入队操作比较简单,首先获取独占锁,然后进行优先级队列的插入操作,调整堆,然后调用available的signal方法,唤醒阻塞队列中的线程。最后释放锁。
- 出队:出队的操作相对繁琐,首先获取队头元素,如果为null,那么就调用available的await操作进行阻塞,等待队列中有入队的操作唤醒。如果不为空,那么验证是否已经到了延时的时间,如果到了,那么就正常出队,调整堆,弹出元素。如果没有到,那么会看是否leader不为空,如果不为空那么说明已经有其他的线程在进行take出队的操作,那么继续等待,这里因为下面调用到了awaitnanos这个方式,会放弃锁,但是其实已经设置了leader,所以这里需要判断leader是否不为空,如果为空则继续无限期等待,等待awaitnanos的线程到时间唤醒进行出队操作。那么如果说leader为空的话,并且没有到时间,则调用awaitnanos,进行定时唤醒,并且leader为当前线程,放弃锁。

leader变量其实是leader-follwers模式的一个变体,用于尽量减少不必要的等待,当一个线程调用队列的take方法变为leader线程后,会调用available.awaitnanos(delay)等待delay时间,其他followers线程会调用await进行无限等待。leader线程到时间,会退出take方法,并且会available.signal唤醒一个follwer线程成为新的leader线程。
下面贴出一个使用范例:


DelayQueue使用PriorityQueue存放数据,使用ReentrantLock来实现线程同步。另外,队列里面的元素需要实现Delayed接口,一个方法是用来返回过期的剩余时间,一个是用来比较元素优先级。