java Queue接口简介

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 +
                    '}';
        }
    }
  • 0
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值