Queue继承自Collection接口,所以支持add,remove操作。但是不建议使用,Queue接口提供了offer,poll和peek方法来替代。offer表示添加,poll表示移除,peek表示查看但不移除。
Collection的add等方法在失败时会抛出异常,但是offer等方法在失败时不会抛异常,会在返回值中表现出来。比如offer,成功返回true,失败返回false。poll方法,队列为空时返回null。
值得一提的是,LinkedList也实现了Queue接口,所以LinkedList也可以当作队列来用。
下面介绍几个实现Queue接口的类
LinkedList
LinkedList内部是链表,下面的链表节点的定义。
private static class Entry<E> {
E element;
Entry<E> next;
Entry<E> previous;
Entry(E element, Entry<E> next, Entry<E> previous) {
this.element = element;
this.next = next;
this.previous = previous;
}
}
值得一提的是,LinkedList还实现了Deque接口,也就是实现了双向队列。LinkedList是线程不安全的队列。
因为LinkedList实现了List接口,所以把LinkedList单独拿出来说。
下面介绍Queue下面的四大接口
BlockingDeque, BlockingQueue, Deque, TransferQueue
从接口可以看出,Queue可以被划分为阻塞的跟非阻塞的。
ArrayDeque
transient Object[] elements;
transient int head;
transient int tail;
head指队列最低下标位置,tail指队列最高下标的后一个位置。可以在纸上画一下。至于tail为什么要指向下一位,这里先买个关子,这个是有深刻用意的。
看其内部原理,只需要看一个方法就能知道个大概。先要说一下,底层用的是循环数组,也就是说head不一定要比tail小。
public void addLast(E e) {
if (e == null)
throw new NullPointerException();
elements[tail] = e;
if ( (tail = (tail + 1) & (elements.length - 1)) == head)
doubleCapacity();
}
从这里可以看出,ArrayDeque不支持null,直接抛异常。
后面的代码很不好读,我刚开始也读懵了。后来看别人的解释才明白。首先,把e赋给tail位置,这个也就是为啥tail非要指向下一位的原因。就是保证数组在任何时候空间都是富余的,至少多一个。这样来了新数后,可以直接放。
最后才考虑要不要扩容的问题。要看懂if里面的语句,先要明白这样一个事实,elements数组的长度是2的n次方。也就是说,length-1换算成二进制,全部都是1。(tail+1)&(length -1),有两种情况,要么等于tail+1,要么等于0。这样写的好处是很巧妙的避免了数组越界问题。
最后就看doubleCapacity()方法了,从名称也可以看出来,是把空间扩充一倍。原理很简单,先上代码
private void doubleCapacity() {
assert head == tail;
int p = head;
int n = elements.length;
int r = n - p; // 数组中位于head右边的元素的个数
int newCapacity = n << 1;//数组扩充一倍
if (newCapacity < 0)//就是说,如果数组最大是2^31-1,不能再大了,也可以说是Integer.Max_Value
throw new IllegalStateException("Sorry, deque too big");
Object[] a = new Object[newCapacity];
System.arraycopy(elements, p, a, 0, r);//copy右边的
System.arraycopy(elements, 0, a, r, p);//copy左边的
elements = a;
head = 0;
tail = n;
}
我看到不少文章上说ArrayDeque的容量是无限的。从源码可以看出,是有限的。
ArrayDeque是线程不安全的,如果没有安全要求,使用队列时,应该首先选择ArrayDeque。
jdk说ArrayDeque在用作队列时比LinkedList要快,我其实不太理解为什么说,请阅读本文的读者不吝赐教。
下面介绍一下concurrent并发包下面的的queue
主要分阻塞的跟非阻塞的
非阻塞的常用的有ConcurrentLinkedQueue
这是一个高并发队列,从名字也可以看出,为了高并发而设计,用的是链表结构。
它号称是高并发环境性能最好的队列,没有之一。它之所以能有很好的性能,是因为其内部复杂的实现。
在对Node进行操作时,使用了cas操作。如在进行节点注入值时,就用了casItem跟casNext方法。
阻塞队列有:
ArrayBlockingQueue:基于数组实现的阻塞队列。内部维护着一个定长数组,没有实现读写分离,意味着生产跟消费是不能并行的。可以先进先出也可以先进后出,长度是需要定义的,是个有界队列。在很多场合非常适合使用。如果是用FIFO策略,就要在构造函数中将公平设为true,默认是非公平的。offer跟poll方法可以带时间参数,表示阻塞时间,如果不带参数,表示立即返回(即不阻塞,意味着offer的时候会失败,数据丢失)
LinkedBlockingQueue:基于链表的阻塞队列,其内部实现了读写分离,在并发环境中是高效的。生产者跟消费者可以实现并行。他是一个无界队列。但在构造方法中也可以指定固定长度。
PriorityBlockingQueue:基于优先级的阻塞队列,但不是排序队列,队列没有排序,是在调用poll方法时,临时去遍历存储结构,找到最小值(如果元素没有实现Comparable接口,优先级的判断通过构造函数传入的Comparetor对象来决定),如果传入的队列的对象实现了Comparable接口,就不用再传入构造器,内部控制线程同步的锁采用公平锁,这个也是一个无界的队列。不过既然是阻塞的,就肯定能够指定固定长度。
DelayQueue:带有时间延迟的队列,其中的元素只有当其指定的延迟时间到了,才能够从队列中获取到元素。这个也是一个无界队列。队列元素必须实现Delayed接口,应用场合很多,比如对缓存超时的数据进行移除,任务超市处理,空闲连接的关闭等。
SynchronousQueue:一种没有缓冲的队列,生产者产生的数据直接会被消费者获取并消费。也就是说得先调用poll,再调用offer,否则就出错了。
下面是ArrayBlockingQueue的例子
public static void main(String[] args) {
//只有是公平锁时,才能实现FIFO
final BlockingQueue<Integer> blockingQueue = new ArrayBlockingQueue<Integer>(3, true);
blockingQueue.offer(1);
blockingQueue.offer(3);
blockingQueue.offer(2);
new Thread(new Runnable() {
public void run() {
try {
Thread.sleep(2000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("出队:" + blockingQueue.poll());
}
}).start();
try {
//如果不带时间参数,意味着不阻塞,则会丢失数据
blockingQueue.offer(4,3L, TimeUnit.SECONDS);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("队列长度 " + blockingQueue.size());
}
下面是PriorityQueue的例子
public void test() {
//String已经实现了Comparable接口,所以就不必再传入比较器
BlockingQueue<String> blockingQueue = new PriorityBlockingQueue<String>(10);
blockingQueue.offer("a");
blockingQueue.offer("c");
blockingQueue.offer("b");
int length = blockingQueue.size();
for (int i = 0; i < length; i++) {
System.out.println(blockingQueue.poll());
}
}
下面是DelayQueue的例子
@Test
public void test1() throws InterruptedException {
/**
* 模拟玩倒计时游戏
*/
BlockingQueue<Player> blockingQueue = new DelayQueue<Player>();
blockingQueue.offer(new Player(3));
blockingQueue.offer(new Player(1));
blockingQueue.offer(new Player(2));
blockingQueue.offer(new Player(5));
int length = blockingQueue.size();
for (int i = 0; i < length; i++) {
//等待时间是10s,如果10s取不到,则会返回null
System.out.println(blockingQueue.poll(10, TimeUnit.SECONDS));
}
}
/**
* 玩的时间一过,就会下线,是个倒计时游戏
*/
private static class Player implements Delayed {
//截止时间,时间是秒
private long deadlineSeconds;
//玩的时间
private long seconds;
//时间类型
TimeUnit unit = TimeUnit.SECONDS;
public Player(long seconds) {
this.seconds = seconds;
this.deadlineSeconds = seconds + unit.convert(System.currentTimeMillis(), TimeUnit.MILLISECONDS);
}
//到期时间,内部会不断的扫描到期时间,然后把到期的给poll
public long getDelay(TimeUnit unit) {
return deadlineSeconds - TimeUnit.SECONDS.convert(System.currentTimeMillis(), TimeUnit.MILLISECONDS);
}
//队列内部的排序方式
public int compareTo(Delayed o) {
Player player = (Player)o;
return this.seconds - player.seconds > 0 ? 1 : (this.seconds - player.seconds == 0 ? 0 : -1);
}
@Override
public String toString() {
return "Player{" +
"seconds=" + seconds +
'}';
}
}