Queue
目录
概述
- 有界无界:
- 有界队列:队列大小是有限制的
- ArrayBlockingQueue:数组实现,满了就通过锁阻塞等待
- LinkedBlockingQueue:虽然是有界队列,但是容量是个非常大的值,根本不会满。 满了也是通过锁阻塞
- 无界队列:
- PriorityQueue:数组实现无界队列,满了就扩容
- ConcurrentLinkedQueue:链表实现的无界队列,没有容量限制
- DelayQueue:底层通过PriorityQueue实现
- SynchronousQueue:内部通过node维护的链表或者栈,是无界的
- PriorityBlockingQueue:底层通过PriorityQueue实现
- 有界队列:队列大小是有限制的
- 线程安全:
- 线程安全:
- ArrayBlockingQueue:添加删除同一把锁ReentrantLock
- LinkedBlockingQueue:添加删除两把锁
- ConcurrentLinkedQueue:通过cas实现,因为tail节点更新机制,所以是弱一致性
- DelayQueue:通过ReentrantLock将更新操作锁住
- SynchronousQueue:内部通过cas操作
- PriorityBlockingQueue:添加删除同一把锁ReentrantLock
- 线程不安全:
- PriorityQueue:modCount
- 线程安全:
public interface Queue<E> extends Collection<E> {
// 增加一个元索 ,如果队列已满,则抛出一个IIIegaISlabEepeplian异常
boolean add(E e);
//添加一个元素并返回true,如果队列已满,则返回false
boolean offer(E e);
//移除并返回队列头部的元素,如果队列为空,则抛出一个NoSuchElementException异常
E remove();
//移除并返问队列头部的元素 ,如果队列为空,则返回null
E poll();
//返回队列头部的元素,如果队列为空,则抛出一个NoSuchElementException异常
E element();
//返回队列头部的元素 ,如果队列为空,则返回null
E peek();
}
- 对于抛异常的方法,在实现类里边的实现是阻塞
PriorityQueue
使用示例
class Person implements Comparable{
private String name;
private int age;
Person(String name,int age){
this.name = name;
this.age = age;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
@Override
public int compareTo(Object o) {
Person person = (Person)o;
return this.age - person.getAge();//按照名字排序
}
}
public static void main(String[] args) {
Queue<Person> priorityQueue = new PriorityQueue();
Person p1 = new Person("a",1);
Person p2 = new Person("b",2);
Person p3 = new Person("c",3);
Person p4 = new Person("d",4);
priorityQueue.add(p2);
priorityQueue.add(p1);
priorityQueue.add(p4);
priorityQueue.add(p3);
Person person = priorityQueue.poll();
while (person!=null){
System.out.println(person.getName());
person = priorityQueue.poll();
}
}
底层排序是通过堆实现的。
属性有哪些
public class PriorityQueue<E> extends AbstractQueue<E>
implements java.io.Serializable {
private static final int DEFAULT_INITIAL_CAPACITY = 11;
transient Object[] queue; // non-private to simplify nested class access
private int size = 0;
private final Comparator<? super E> comparator;
transient int modCount = 0; // non-private to simplify nested class access
}
- object数组queue
- 默认DEFAULT_INITIAL_CAPACITY:11
- 大小size
- 比较器comparator
- fail-fast计数器modCount:非线程安全
构造器
public PriorityQueue() {
this(11, (Comparator)null);
}
public PriorityQueue(int var1) {
this(var1, (Comparator)null);
}
public PriorityQueue(Comparator<? super E> var1) {
this(11, var1);
}
public PriorityQueue(int var1, Comparator<? super E> var2) {
this.size = 0;
this.modCount = 0;
if (var1 < 1) {
throw new IllegalArgumentException();
} else {
this.queue = new Object[var1];
this.comparator = var2;
}
}
public PriorityQueue(Collection<? extends E> var1) {
this.size = 0;
this.modCount = 0;
if (var1 instanceof SortedSet) {
SortedSet var2 = (SortedSet)var1;
this.comparator = var2.comparator();
this.initElementsFromCollection(var2);
} else if (var1 instanceof PriorityQueue) {
PriorityQueue var3 = (PriorityQueue)var1;
this.comparator = var3.comparator();
this.initFromPriorityQueue(var3);
} else {
this.comparator = null;
this.initFromCollection(var1);
}
}
public PriorityQueue(PriorityQueue<? extends E> var1) {
this.size = 0;
this.modCount = 0;
this.comparator = var1.comparator();
this.initFromPriorityQueue(var1);
}
public PriorityQueue(SortedSet<? extends E> var1) {
this.size = 0;
this.modCount = 0;
this.comparator = var1.comparator();
this.initElementsFromCollection(var1);
}
- 有7个构造器:
- 无参构造器:
- 指定容量:
- 指定比较器:
- 指定容量和比较器:
- 按照入参collection进行初始化:判断collection是哪种类型的
- PriorityQueue:直接强转成PriorityQueue
- SortedSet:通过Arrays.copyOf转换成Object数组
- 两种都不属于:因为这种情况初始进来的元素是无序的,初始化最后一步要进行一次排序
- 按照入参PriorityQueue进行初始化:
- 按照入参SortedSet进行初始化:
添加方法
public boolean offer(E e) {
if (e == null)
throw new NullPointerException();
modCount++;
int i = size;
if (i >= queue.length)
grow(i + 1);//扩容:类似ArrayList,通过Arrays.copyOf方法
size = i + 1;
if (i == 0)
queue[0] = e;
else
siftUp(i, e);//调整堆,过程参考堆排序链接
return true;
}
- add和offer调用的是同一个方法
- 逻辑同ArrayList一样,最后进行一下排序即可
ArrayBlockingQueue
属性:
public class ArrayBlockingQueue<E> extends AbstractQueue<E>
implements BlockingQueue<E>, java.io.Serializable {
private static final long serialVersionUID = -817911632652898426L;
final Object[] items;
/** take, poll, peek or remove的下一个索引 */
int takeIndex;
/** put, offer, or add的下一个索引 */
int putIndex;
/**队列中元素个数*/
int count;
final ReentrantLock lock;
private final Condition notEmpty;
private final Condition notFull;
transient java.util.concurrent.ArrayBlockingQueue.Itrs itrs = null;
}
原理:
以下是自己实现的一个简单版本的ArrayBlockQueue帮助理解:
public class ArrayBlockQueue {
private Object[] items;
private int size;
private int sizeMax;
private Lock lock = new ReentrantLock();
private Condition inLock = lock.newCondition();
private Condition outLock = lock.newCondition();
public ArrayBlockQueue(int sizeMax){
this.sizeMax = sizeMax;
items = new Object[sizeMax];
}
public void add(Object o){
lock.lock();
try{
if(size == sizeMax){
inLock.await();
System.out.println("我满了");
}
size++;
items[size-1] = o;
System.out.println("成功添加元素:"+o.toString());
if(sizeMax>0){
System.out.println("队列中有数据了,获取线程可以来拿数据了");
outLock.signal();
}
}catch (Exception e){
e.printStackTrace();
}finally {
lock.unlock();
}
}
public Object get(){
lock.lock();
Object result = null;
try{
if(size==0){
outLock.await();
System.out.println("我空了");
}
size--;
result = items[size];
items[size] = null;
System.out.println("成功获取一个元素:"+ result.toString());
if(size<sizeMax){
System.out.println("队列不满了,添加线程可以加元素了");
inLock.signal();
}
}catch (Exception e){
e.printStackTrace();
}finally {
lock.unlock();
}
return result;
}
public static void main(String[] args) {
/***
* 创建一个队列
* 开启三个线程,往队列中添加元素,一旦队列满了,则添加线程阻塞
* 开启一个线程,从队列中拿取元素,一旦队列空了,则拿取线程阻塞
* 添加元素的时候,发现队列中有元素了,通知获取线程可以来拿元素了
* 拿取元素的时候,发现队列元素不满了,通知添加线程可以来添加元素了
*/
ArrayBlockQueue arrayBlockQueue = new ArrayBlockQueue(2);
Thread t1 = new Thread(new Runnable() {
@Override
public void run() {
arrayBlockQueue.add("t1");
}
});
Thread t2 = new Thread(new Runnable() {
@Override
public void run() {
arrayBlockQueue.add("t2");
}
});
Thread t3 = new Thread(new Runnable() {
@Override
public void run() {
arrayBlockQueue.add("t3");
}
});
Thread t4 = new Thread(new Runnable() {
@Override
public void run() {
arrayBlockQueue.get();
}
});
t1.start();//添加
t2.start();//添加
t3.start();//添加
t4.start();//获取
}
}
LinkedBlockingQueue
public static void main(String[] args) {
LinkedBlockingQueue linkedBlockingQueue = new LinkedBlockingQueue();
linkedBlockingQueue.offer(1);
linkedBlockingQueue.add(1);
linkedBlockingQueue.poll();
linkedBlockingQueue.remove();
linkedBlockingQueue.peek();
linkedBlockingQueue.element();
}
public class LinkedBlockingQueue<E> extends AbstractQueue<E>
implements BlockingQueue<E>, java.io.Serializable {
static class Node<E> {
E item;
java.util.concurrent.LinkedBlockingQueue.Node<E> next;
Node(E x) {
item = x;
}
}
//容量
private final int capacity;
//已存入的元素数量
private final AtomicInteger count = new AtomicInteger();
transient java.util.concurrent.LinkedBlockingQueue.Node<E> head;
private transient java.util.concurrent.LinkedBlockingQueue.Node<E> last;
//take和put是两把锁 -- 所以性能上比ArrayBlockingQueue要好一些
private final ReentrantLock takeLock = new ReentrantLock();
private final Condition notEmpty = takeLock.newCondition();
private final ReentrantLock putLock = new ReentrantLock();
private final Condition notFull = putLock.newCondition();
private void signalNotEmpty() {
final ReentrantLock takeLock = this.takeLock;
takeLock.lock();
try {
notEmpty.signal();
} finally {
takeLock.unlock();
}
}
private void signalNotFull() {
final ReentrantLock putLock = this.putLock;
putLock.lock();
try {
notFull.signal();
} finally {
putLock.unlock();
}
}
}
构造器:
public LinkedBlockingQueue() {
this(Integer.MAX_VALUE);
}
public LinkedBlockingQueue(int capacity) {
if (capacity <= 0) throw new IllegalArgumentException();
this.capacity = capacity;
//创建一个内容为null的node,将链表头和尾都指向该node
last = head = new LinkedBlockingQueue.Node<E>(null);
}
offer:
public boolean offer(E e) {
if (e == null) throw new NullPointerException();
final AtomicInteger count = this.count;
if (count.get() == capacity)
return false;
int c = -1;
LinkedBlockingQueue.Node<E> node = new LinkedBlockingQueue.Node<E>(e);
//加锁
final ReentrantLock putLock = this.putLock;
putLock.lock();
try {
if (count.get() < capacity) {
//向链表尾部插入node
enqueue(node);
//size++
c = count.getAndIncrement();
if (c + 1 < capacity)
//如果链表没满,通知阻塞的添加线程
notFull.signal();
}
} finally {
putLock.unlock();
}
if (c == 0)//初始值是-1,如果等于0代表上面添加元素成功,链表不为空了,通知阻塞的take线程
signalNotEmpty();
return c >= 0;
}
private void enqueue(LinkedBlockingQueue.Node<E> node) {
last = last.next = node;
}
ConcurrentLinkedQueue
看下属性
public class ConcurrentLinkedQueue<E> extends AbstractQueue<E> implements Queue<E>, Serializable {
private transient volatile java.util.concurrent.ConcurrentLinkedQueue.Node<E> head;
private transient volatile java.util.concurrent.ConcurrentLinkedQueue.Node<E> tail;
private static final Unsafe UNSAFE;
private static class Node<E> {
volatile E item;
volatile java.util.concurrent.ConcurrentLinkedQueue.Node<E> next;
}
}
- 链表自然包含头节点和尾节点,内部类node,单向链表持有下个节点的引用
- Unsafe表明更新是通过cas更新,也表明是线程安全的队列
添加方法
public boolean offer(E e) {
// 如果e为null,则直接抛出NullPointerException异常
checkNotNull(e);
// 创建入队节点
final Node<E> newNode = new Node<E>(e);
// 循环CAS直到入队成功
// 1、根据tail节点定位出尾节点(last node);2、将新节点置为尾节点的下一个节点;3、casTail更新尾节点
for (Node<E> t = tail, p = t;;) {
// p用来表示队列的尾节点,初始情况下等于tail节点
// q是p的next节点
Node<E> q = p.next;
// 判断p是不是尾节点,tail节点不一定是尾节点,判断是不是尾节点的依据是该节点的next是不是null
// 如果p是尾节点
if (q == null) {
// p is last node
// 设置p节点的下一个节点为新节点,设置成功则casNext返回true;否则返回false,说明有其他线程更新过尾节点
if (p.casNext(null, newNode)) {
// Successful CAS is the linearization point
// for e to become an element of this queue,
// and for newNode to become "live".
// 如果p != t,则将入队节点设置成tail节点,更新失败了也没关系,因为失败了表示有其他线程成功更新了tail节点
if (p != t) // hop two nodes at a time
casTail(t, newNode); // Failure is OK.
return true;
}
// Lost CAS race to another thread; re-read next
}
// 多线程操作时候,由于poll时候会把旧的head变为自引用,然后将head的next设置为新的head
// 所以这里需要重新找新的head,因为新的head后面的节点才是激活的节点
else if (p == q)
// We have fallen off list. If tail is unchanged, it
// will also be off-list, in which case we need to
// jump to head, from which all live nodes are always
// reachable. Else the new tail is a better bet.
p = (t != (t = tail)) ? t : head;
// 寻找尾节点
else
// Check for tail updates after two hops.
p = (p != t && t != (t = tail)) ? t : q;
}
}
}
先图解一下添加过程
添加过程中有两个更新操作,一个是将新元素插入到队列尾部,第二就是更新tail节点的指向。
但是这里更新tail节点有个判断:只有当tail的next不是null的时候才更新,否则这一步略过
- 初始化的时候,创建一个node,该node即是tail又是head, 它的next指向null
- 添加元素1,将head的next指向元素1,因为head和tail是同一个,所以tail的next是元素1,不是null,所以不更新tail
- 添加元素2,根据tail节点找到其next为元素1,判断元素1是当前最后一个节点,将元素1next指向元素2,因为tail的next节点不是null,所以更新tail指向元素2
我们发现并不是每一步都更新tail节点,为什么这么设计?
因为在多线程环境下这里的更新采用cas操作,每次更新可能失败,失败就需要重试,多重试一次就影响性能,所以通过这种方式减少了更新操作(一次添加需要两个更新,但是这里有时候把更新tail省略了),所以通过这种方式来提升入队性能。
DelayQueue
- 加入其中的元素必需实现Delayed接口。当生产者线程调用put之类的方法加入元素时,会触发Delayed接口中的compareTo方法进行排序,也就是说队列中元素的顺序是按到期时间排序的,而非它们进入队列的顺序。排在队列头部的元素是最早到期的,越往后到期时间赿晚。
- 消费者线程查看队列头部的元素,注意是查看不是取出。然后调用元素的getDelay方法,如果此方法返回的值小0或者等于0,则消费者线程会从队列中取出此元素,并进行处理。如果getDelay方法返回的值大于0,则消费者线程wait返回的时间值后,再从队列头部取出元素,此时元素应该已经到期。
属性有哪些
public class DelayQueue<E extends Delayed> extends AbstractQueue<E> implements BlockingQueue<E> {
private final transient ReentrantLock lock = new ReentrantLock();
//底层数据结构是PriorityQueue
private final PriorityQueue<E> q = new PriorityQueue();
//等待获取队列元素的线程。
private Thread leader = null;
private final Condition available;
}
- 底层是通过PriorityQueue来存储数据的,所以DelayQueue也是个无界队列
- leader:消费者线程来队列取数据的时候,如果待出队元素还未到过期时间,通过awaitNanos()阻塞这个线程,并且这里给存储下来。
- lock:所有更新操作要锁住
添加方法:
public boolean offer(E e) {
final ReentrantLock lock = this.lock;
lock.lock();
try {
q.offer(e); //放一个元素
if (q.peek() == e) {
leader = null; // leader 线程为空
available.signal(); //其他线程唤醒,如果队首元素是刚插入的元素,则设置leader为null,并唤醒阻塞在available上的线程
}
return true;
} finally {
lock.unlock();
}
}
- 将元素插入到内部的PriorityQueue,PriorityQueue会自动进行排序
- 如果插入的元素就是下一个即将到期的元素,将leader设置为null,并且唤醒原leader线程。
- 这个是什么场景:
- 消费者线程T1来获取元素,当前没有已经到期的元素,则将T1阻塞。
- 插入的元素Node1是即将到期元素(或者已经到期),唤醒T1
- T1判断Node1是否到期,如果到期则进行处理,如果没到期再次阻塞
- 这个是什么场景:
看下阻塞的获取方法
public E take() throws InterruptedException {
ReentrantLock var1 = this.lock;
var1.lockInterruptibly();
try {
while(true) {
while(true) {
Delayed var2 = (Delayed)this.q.peek();
if (var2 != null) {
long var3 = var2.getDelay(TimeUnit.NANOSECONDS);
if (var3 <= 0L) {
Delayed var14 = (Delayed)this.q.poll();
return var14;
}
var2 = null;
if (this.leader != null) {
this.available.await();
} else {
Thread var5 = Thread.currentThread();
this.leader = var5;
try {
this.available.awaitNanos(var3);
} finally {
if (this.leader == var5) {
this.leader = null;
}
}
}
} else {
this.available.await();
}
}
}
} finally {
//leader为null并且队列不为空,说明没有其他线程在等待,那就通知条件队列
if (this.leader == null && this.q.peek() != null) {
this.available.signal();
}
var1.unlock();
}
}
- 消费者线程来获取元素的时候,首先开启个while循环
- peek一下元素,如果没有peek到,证明队列是空的,直接阻塞线程
- 如果peek到了元素,继续判断该元素是否到期,如果已经到期,直接返回该元素
- 如果元素没有到期,判断leader是否为null,如果为null,更新当前线程为leader,并且通过awaitNanos阻塞(leader线程的作用就是这个,会根据元素到期时间自动唤醒)
- 如果leader不为null,直接阻塞线程即可(当有多个线程的时候,都各自阻塞在这里,当队列有元素后会唤醒所有阻塞在这的线程,这些线程在继续第一步开启的while循环,pk获取元素)
- 线程执行完毕后在finally里要通知一下其他阻塞队列
SynchronousQueue
也是一个队列来,但它的特别之处在于它内部没有容器,一个生产线程,当它生产产品(即put的时候),
如果当前没有人想要消费产品(即当前没有线程执行take),此生产线程必须阻塞,等待一个消费线程调用take操作,
take操作将会唤醒该生产线程,同时消费线程会获取生产线程的产品(即数据传递),这样的一个过程称为一次配对过程(当然也可以先take后put,原理是一样的)。
看一下属性
public class SynchronousQueue<E> extends AbstractQueue<E> implements BlockingQueue<E>, Serializable {
private transient volatile java.util.concurrent.SynchronousQueue.Transferer<E> transferer;
private ReentrantLock qlock;
private java.util.concurrent.SynchronousQueue.WaitQueue waitingProducers;
private java.util.concurrent.SynchronousQueue.WaitQueue waitingConsumers;
}
- 我们只关注一个属性Transferer:这个是内部类,核心逻辑都是它实现的。它是一个抽象类,有两个实现类:
- TransferQueue:内部通过链表实现的公平队列(先进先出)
- TransferStack:内部通过栈实现的非公平队列(先进后出)
- 所有的添加、获取方法内部都是调用的Transferer的transferer方法。
其实内部是由Node节点维护一个队列用来存储数据,每个node代表一个线程。
先看下TransferQueue实现的公平模式:
- 初始状态下
- 线程put1执行put(1)操作,由于当前没有配对的消费线程,所以put1线程入队列,自旋一小会后睡眠等待,这时队列状态如下
- 接着,线程put2执行了put(2)操作,跟前面一样,put2线程入队列,自旋一小会后睡眠等待,这时队列状态如下
- 这时候,来了一个线程take1,执行了 take操作,由于tail指向put2线程,put2线程跟take1线程配对了,这时take1线程不需要入队。
但是请注意了,这时候,要唤醒的线程并不是put2,而是put1。为何?
大家应该知道我们现在讲的是公平策略,所谓公平就是谁先入队了,谁就优先被唤醒,我们的例子明显是put1应该优先被唤醒。
公平策略总结下来就是:队尾匹配队头出队。
执行后put1线程被唤醒,take1线程的 take()方法返回了1(put1线程的数据),这样就实现了线程间的一对一通信,这时候内部状态如下:
TransferStack非公平模式:
- 线程put1执行 put(1)操作,由于当前没有配对的消费线程,所以put1线程入栈,自旋一小会后睡眠等待,这时栈状态如下:
- 接着,线程put2再次执行了put(2)操作,跟前面一样,put2线程入栈,自旋一小会后睡眠等待,这时栈状态如下:
- 这时候,来了一个线程take1,执行了take操作,这时候发现栈顶为put2线程,匹配成功,但是实现会先把take1线程入栈,然后take1线程循环执行匹配put2线程逻辑,一旦发现没有并发冲突,就会把栈顶指针直接指向 put1线程
- 最后,再来一个线程take2,执行take操作,这跟步骤3的逻辑基本是一致的,take2线程入栈,然后在循环中匹配put1线程,最终全部匹配完毕,栈变为空,恢复初始状态,如下图所示:
PriorityBlockingQueue
锁的原理和ArrayBlockQueue一样的都是一把锁。
元素操作同PriorityQueue一样。