并发编程学习——3 队列

队列

queue

public interface Queue<E> extends Collection<E> {
    /**
     * 向队列中添加元素
     */
    boolean add(E e);

    /**
     * 向队列中添加元素,成功后返回true失败后返回false
     */
    boolean offer(E e);

    /**
     * 移除并返回头部元素,获取失败抛出异常
     */
    E remove();

    /**
     * 移除并返回头部元素,获取失败返回null
     */
    E poll();

    /**
     * 获取头部元素,失败则抛出异常
     */
    E element();

    /**
     * 获取头部元素,失败则返回null
     */
    E peek();
}

类图

在这里插入图片描述

AbstractQueue

队列实现类的基类,抽象类,实现了部分方法

public abstract class AbstractQueue<E>
    extends AbstractCollection<E>
    implements Queue<E> {

    /**
     * Constructor for use by subclasses.
     */
    protected AbstractQueue() {
    }

    /**
     * 主要逻辑交给了offer来实现,添加了异常抛出动作
     */
    public boolean add(E e) {
        if (offer(e))
            return true;
        else
            throw new IllegalStateException("Queue full");
    }

    /**
     * 主要逻辑交给了poll来实现,添加了异常抛出动作
     */
    public E remove() {
        E x = poll();
        if (x != null)
            return x;
        else
            throw new NoSuchElementException();
    }

    /**
     * 主要逻辑交给了peek来实现,添加了异常抛出动作
     */
    public E element() {
        E x = peek();
        if (x != null)
            return x;
        else
            throw new NoSuchElementException();
    }

    /**
     * 循环调用poll来进行清空
     */
    public void clear() {
        while (poll() != null)
            ;
    }

    /**
     *
     */
    public boolean addAll(Collection<? extends E> c) {
        if (c == null)
            throw new NullPointerException();
        if (c == this)
            throw new IllegalArgumentException();
        boolean modified = false;
        for (E e : c)
            if (add(e))
                modified = true;
        return modified;
    }

}

可以看到抽象类基本上是通过offer、poll、peek这三个方法复合处理实现了其他方法逻辑,这几个方法具体实现在子类。

PriorityQueue

PriorityQueue 一个基于优先级的无界优先级队列。优先级队列的元素按照其自然顺序进行排序,或者根据构造队列时提供的 Comparator 进行排序

简单的使用


    PriorityQueue queue = new PriorityQueue();

    PriorityQueue queue2;

    Comparator comparator = new Comparator() {
        @Override 
        public int compare(Object o1, Object o2) {
            // 自定义的排序方式
            return 0;
        }
    };
    
    public void getQueue2 () {
        /**
         * 创建一个长度为11 ,使用comparator 规则进行排序取值的队列
         */
        queue2 = new PriorityQueue(comparator);
    }

假如不设置排序策略JAVA会有一个默认的策略

    public static void main(String[] args) {
        PriorityQueueTest test = new PriorityQueueTest();
        test.queue.add(1);
        test.queue.add(3);
        test.queue.add(2);
        System.out.println(test.queue.poll());
        System.out.println(test.queue.poll());
        System.out.println(test.queue.poll());
        System.out.println("继续放入参数");
    }

输出

1
2
3
继续放入参数
排序时机
    public boolean offer(E e) {
        if (e == null)
            throw new NullPointerException();
        modCount++;
        int i = size;
        if (i >= queue.length)
            grow(i + 1);
        size = i + 1;
        if (i == 0)
            queue[0] = e;
        else
            siftUp(i, e);
        return true;
    }
    
    public E poll() {
        if (size == 0)
            return null;
        int s = --size;
        modCount++;
        E result = (E) queue[0];
        E x = (E) queue[s];
        queue[s] = null;
        if (s != 0)
            siftDown(0, x);
        return result;
    }
    private void siftUp(int k, E x) {
        if (comparator != null)
            siftUpUsingComparator(k, x);
        else
            siftUpComparable(k, x);
    }

可以看到无论是取还是放入都会调用siftUp进行排序,PriorityQueue不能保证队列顺序,但是可以保证取出的头部元素顺序。

SynchronousQueue

是一种线程安全的,阻塞的无缓冲的等待队列,该SynchronousQueue必须在某次添加元素后必须等待其他线程取走后才能继续添加,反之亦然

其isEmpty,size,remainingCapacity,clear方法固定

    /**
     *
     */
    public boolean isEmpty() {
        return true;
    }

    /**
     *
     */
    public int size() {
        return 0;
    }

    /**
     */
    public int remainingCapacity() {
        return 0;
    }

    /**
     */
    public void clear() {
    }
构造方法

其根据是否公平的参数初始化队列(TransferQueue)或者栈(TransferStack),公平排序策略是指调用put的线程之间,或take的线程之间

    public SynchronousQueue(boolean fair) {
        transferer = fair ? new TransferQueue<E>() : new TransferStack<E>();
    }

BlockingQueue

BlockingQueue扩展了Queue,增加了可阻塞的插入和获取等操作。如果队列为空,那么获取元素的操作将一直阻塞,知道队列中出现一个可用的元素,如果队列已经满,那么插入元素的操作将一直阻塞,知道队列中出现可用空间。

// 新增的接口
public interface BlockingQueue<E> extends Queue<E> {


    /**
     * 可被阻塞的添加元素方法,当队列满的时候将被阻塞
     */
    void put(E e) throws InterruptedException;

    /**
     * 设置参数,但是可以设置超时时间
     */
    boolean offer(E e, long timeout, TimeUnit unit)
        throws InterruptedException;

    /**
     * 取出队列头部元素的方法,当元素为空的时候会被阻塞
     */
    E take() throws InterruptedException;

    /**
     * 取出队列头部元素的方法,假如设置了超时时间,
     * 在时间内假如取不出元素则抛出null
     */
    E poll(long timeout, TimeUnit unit)
        throws InterruptedException;

    /**
     * 返回队列容量
     */
    int remainingCapacity();

    /**
     * 
     */
    public boolean contains(Object o);

    /**
     * 获取队列中所有元素并放置到C中
     */
    int drainTo(Collection<? super E> c);

    /**
     * 获取队列中指定数量的元素,并放置到C中
     */
    int drainTo(Collection<? super E> c, int maxElements);
}

LinkedBlockingQueue

LinkedBlockingQueue是一个阻塞队列,内部基于链表来存放元素

    public LinkedBlockingQueue() {
        this(Integer.MAX_VALUE);
    }

    /**
     * Creates a {@code LinkedBlockingQueue} with the given (fixed) capacity.
     *
     * @param capacity the capacity of this queue
     * @throws IllegalArgumentException if {@code capacity} is not greater
     *         than zero
     */
    public LinkedBlockingQueue(int capacity) {
        if (capacity <= 0) throw new IllegalArgumentException();
        this.capacity = capacity;
        last = head = new Node<E>(null);
    }

简单的使用

    public void queueTest () throws InterruptedException {
        queue.put(new Object());
        queue.offer(new Object());
        // 设置参数,设置超时时间
        queue.offer(new Object(),5,TimeUnit.SECONDS);
        queue.poll();
        // 取出参数,设置超时时间
        queue.poll(5,TimeUnit.SECONDS);
        // 阻塞的取值
        queue.take();
    }

内部由两个ReentrantLock来实现出入队列的线程安全,由各自的Condition对象(条件监视器)。

    /** Lock held by take, poll, etc */
    private final ReentrantLock takeLock = new ReentrantLock();

    /** Wait queue for waiting takes */
    private final Condition notEmpty = takeLock.newCondition();

    /** Lock held by put, offer, etc */
    private final ReentrantLock putLock = new ReentrantLock();

    /** Wait queue for waiting puts */
    private final Condition notFull = putLock.newCondition();

ArrayBlockingQueue

基于数组的阻塞队列。ArrayBlockingQueue是有界的初始化必须指定大小,而LinkedBlockingQueue可以是有界的也可以是无界的(Integer.MAX_VALUE),对于后者而言,当添加速度大于移除速度时,在无界的情况下,可能会造成内存溢出等问题。

    public ArrayBlockingQueue(int capacity) {
        this(capacity, false);
    }

    public ArrayBlockingQueue(int capacity, boolean fair) {
        if (capacity <= 0)
            throw new IllegalArgumentException();
        this.items = new Object[capacity];
        lock = new ReentrantLock(fair);
        notEmpty = lock.newCondition();
        notFull =  lock.newCondition();
    }

构造函数中必须选择长度

创建队列

    /**
     * 公平的队列
     */
    ArrayBlockingQueue nonfair = new ArrayBlockingQueue(10,false);

    /**
     * 非公平的队列
     */
    ArrayBlockingQueue fair = new ArrayBlockingQueue(10,true);

内部由ReentrantLock来实现线程安全,由Condition来实现等待唤醒的功能。

    /** Main lock guarding all access */
    final ReentrantLock lock;

    /** Condition for waiting takes */
    private final Condition notEmpty;

    /** Condition for waiting puts */
    private final Condition notFull;

放入元素
    public boolean offer(E e, long timeout, TimeUnit unit)
        throws InterruptedException {

        if (e == null) throw new NullPointerException();
        long nanos = unit.toNanos(timeout);
        int c = -1;
        final ReentrantLock putLock = this.putLock;
        final AtomicInteger count = this.count;
        putLock.lockInterruptibly();
        try {
            while (count.get() == capacity) {
                if (nanos <= 0)
                    return false;
                nanos = notFull.awaitNanos(nanos);
            }
            enqueue(new Node<E>(e));
            c = count.getAndIncrement();
            if (c + 1 < capacity)
                notFull.signal();
        } finally {
            putLock.unlock();
        }
        if (c == 0)
            signalNotEmpty();
        return true;
    }

和ArrayBlockingQueue直接修改数组不同,ArrayBlockingQueue添加到链表中的是一个Node对象

PriorityBlockingQueue

这是一个无界有序的阻塞队列,排序规则和之前介绍的PriorityQueue一致,只是增加了阻塞操作

ArrayBlockingQueue和LinkedBlockingQueue

  • ArrayBlockingQueue是有界的初始化必须指定大小,而LinkedBlockingQueue可以是有界的也可以是无界的(Integer.MAX_VALUE),在无界的情况下,可能会造成内存溢出等问题。
  • 数据存储容器不同,ArrayBlockingQueue采用的是数组作为数据存储容器,而LinkedBlockingQueue采用的则是以Node节点作为连接对象的链表。
  • ArrayBlockingQueue采用的是数组存储,因此在插入或删除元素时不会产生或销毁任何额外的对象实例,而LinkedBlockingQueue则会生成一个额外的Node对象。
  • 两者的实现队列添加或移除的锁不一样,ArrayBlockingQueue实现的队列中的锁是没有分离的,即添加操作和移除操作采用的同一个ReenterLock锁,而LinkedBlockingQueue实现的队列中的锁是分离的,其添加采用的是putLock,移除采用的则是takeLock,这样能大大提高队列的吞吐量,也意味着在高并发的情况下生产者和消费者可以并行地操作队列中的数据,以此来提高整个队列的并发性能

Deque

Java 6 增加了两种容器类型,deque和blockingdeque,他们分别对queue和blockingqueue进行了扩展。实现了队列头和尾部的高效插入和移除

两端队列同样适用于另外一种相关模式。工作密取。在生产者-消费者设计中,所有的消费者都有一个共享的工作队列,而工作密取设计中,每个消费者都有鸽子的双端队列,如果一个消费者完成了自己的全部工作,她可以从其他消费者双端队列末尾秘密地获取工作。当工作者线程需要访问另一个队列的时候,它会从队列的尾部而不是头部获取工作,因此进一步降低了队列上的竞争程度。

public interface Deque<E> extends Queue<E> {
    /**
     * 向队列头部添加元素,失败则抛出异常
     */
    void addFirst(E e);

    /**
     * 向队列尾部添加元素,失败则抛出异常
     */
    void addLast(E e);

    /**
     * 向队列头部添加元素,失败false,成功true
     */
    boolean offerFirst(E e);

    /**
     * 向队列尾部添加元素,失败false,成功true
     */
    boolean offerLast(E e);

    /**
     * 移除并获得头部元素,空则抛出异常
     */
    E removeFirst();

    /**
     * 移除并获得尾部元素,空则抛出异常
     */
    E removeLast();

    /**
     * 移除并获得头部元素,空则返回null
     */
    E pollFirst();

    /**
     * 移除并获得尾部元素,空则返回null
     */
    E pollLast();

    /**
     * 获得队列头部元素,空则抛出异常
     */
    E getFirst();

    /**
     * 获得队列尾部元素,空则抛出异常
     */
    E getLast();

    /**
     * 或的队列头部元素
     */
    E peekFirst();

    /**
     * 或的队列头部元素
     */
    E peekLast();

    /**
     * 移除第一个和这个元素匹配的元素
     */
    boolean removeFirstOccurrence(Object o);

    /**
     * 移除最后一个和这个元素匹配的元素
     */
    boolean removeLastOccurrence(Object o);

    // *** Queue methods ***


    // *** Stack methods ***


    // *** Collection methods ***

}

ArrayDeque

基于数组的双端队列

构造函数

假如不设置长度默认为16

    public ArrayDeque() {
        elements = new Object[16];
    }
参数
    /**
     * The index of the element at the head of the deque (which is the
     * element that would be removed by remove() or pop()); or an
     * arbitrary number equal to tail if the deque is empty.
     */
    transient int head;

    /**
     * The index at which the next element would be added to the tail
     * of the deque (via addLast(E), add(E), or push(E)).
     */
    transient int tail;

内部维持两个做引,head为头部索引,tail为尾部的下一个索引。

扩容

可以看到系统每次扩容newCapacity会扩容一倍

    public void addFirst(E e) {
        if (e == null)
            throw new NullPointerException();
        elements[head = (head - 1) & (elements.length - 1)] = e;
        if (head == tail)
            doubleCapacity();
    }

    /**
     * Inserts the specified element at the end of this deque.
     *
     * <p>This method is equivalent to {@link #add}.
     *
     * @param e the element to add
     * @throws NullPointerException if the specified element is null
     */
    public void addLast(E e) {
        if (e == null)
            throw new NullPointerException();
        elements[tail] = e;
        if ( (tail = (tail + 1) & (elements.length - 1)) == head)
            doubleCapacity();
    }
    
    private void doubleCapacity() {
        assert head == tail;
        int p = head;
        int n = elements.length;
        int r = n - p; // number of elements to the right of p
        int newCapacity = n << 1;
        if (newCapacity < 0)
            throw new IllegalStateException("Sorry, deque too big");
        Object[] a = new Object[newCapacity];
        System.arraycopy(elements, p, a, 0, r);
        System.arraycopy(elements, 0, a, r, p);
        elements = a;
        head = 0;
        tail = n;
    }
关于head和tail

正常我们认为数组的头部应该是0尾部就是最后一个有值的位置,但是ArrayDeque的head指是第一个有值的索引单,内部ArrayDeque把数组看成了一个环。当从头部移除元素的时候会让头部索引偏移一个单位

    public E pollFirst() {
        int h = head;
        @SuppressWarnings("unchecked")
        E result = (E) elements[h];
        // Element is null if deque empty
        if (result == null)
            return null;
        elements[h] = null;     // Must null out slot
        head = (h + 1) & (elements.length - 1);
        return result;
    }

而添加元素的时候当head和taill也会发生偏移

// addFirst
elements[head = (head - 1) & (elements.length - 1)] = e;
        if (head == tail)
            doubleCapacity();
            
// addLast
elements[tail] = e;
        if ( (tail = (tail + 1) & (elements.length - 1)) == head)
            doubleCapacity();

这样就能说明容量为什么要为2的倍数,当保证2的n次方的时候,可以保证在n位为1,其他位为0。而这个时候使用elements.length - 1 就可以保证高位(n)为0(假)其他低位为1(真)。这样仅仅通过位与操作就可以完成环形索引的计算,而不需要进行边界的判断,在实现上更为高效。

LinkedBlockingDeque

LinkedBlockingDeque是双向链表实现的双向并发阻塞队列,和ArrayBlockingQueue相同,其内部数据使用Node对象包装。

锁的加载和LinkedBlockingQueue不同之处只使用了一个锁来保证线程安全。

    /** Main lock guarding all access */
    final ReentrantLock lock = new ReentrantLock();

    /** Condition for waiting takes */
    private final Condition notEmpty = lock.newCondition();

    /** Condition for waiting puts */
    private final Condition notFull = lock.newCondition();

ConcurrentLinkedDeque

ConcurrentLinkedDeque是一个双向链表结构的无界并发队列,实现无锁线程安全,底层使用自旋和CAS来保证线程安全

    private void linkFirst(E e) {
        checkNotNull(e);
        final Node<E> newNode = new Node<E>(e);

        restartFromHead:
        for (;;)
            // 循环获得p的前一个节点
            for (Node<E> h = head, p = h, q;;) {
                if ((q = p.prev) != null &&
                    (q = (p = q).prev) != null)
                    // Check for head updates every other hop.
                    // If p == q, we are sure to follow head instead.
                    p = (h != (h = head)) ? h : q;
                else if (p.next == p) // PREV_TERMINATOR
                    // 操作跳转至restartFromHead
                    continue restartFromHead;
                else {
                    // p is first node
                    newNode.lazySetNext(p); // CAS piggyback
                    if (p.casPrev(null, newNode)) {
                        // Successful CAS is the linearization point
                        // for e to become an element of this deque,
                        // and for newNode to become "live".
                        if (p != h) // hop two nodes at a time
                            casHead(h, newNode);  // Failure is OK.
                        return;
                    }
                    // Lost CAS race to another thread; re-read prev
                }
            }
    }
    
    
    // 设置参数
    void lazySetNext(Node<E> val) {
         UNSAFE.putOrderedObject(this, nextOffset, val);
    }
    
    private static final sun.misc.Unsafe UNSAFE;

总结

这一篇很粗略的浏览了部分队列以及其关联对象,看的不算太详细页不算太精通。毕竟代码中各种实现的原理还是有很多可以学习的地方。

阻塞队列

  • ArrayBlockingQueue:一个由数组结构组成的有界阻塞队列;
  • LinkedBlockingQueue:一个由链表结构组成的有界阻塞队列。
  • PriorityBlockingQueue:一个支持优先级排序的无界阻塞队列。
  • SynchronousQueue:一个不存储元素的阻塞队列。

非阻塞队列

  • DelayQueue:是一个支持延时获取元素的无界阻塞队列。
  • PriorityQueue:一个支持优先级排序的无界队列。

双端队列

  • LinkedTransferQueue:一个由由双向链表实现的双向并发阻塞组成的无界阻塞队列,相对于其他阻塞队列,LinkedTransferQueue多了tryTransfer和transfer方法。
  • LinkedBlockingDeque:一个由双向链表实现的双向并发阻塞组成的无界向阻塞队列。
  • ConcurrentLinkedDeque:一个双向链表结构的无界并发队列,实现无锁线程安全
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

大·风

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值