BlockingQueue接口及其实现类的源码分析

      BlockingQueue是一个阻塞队列的接口,提供了一系列的接口方法。其中方法主要可以分为三类,包括Insert相关的add、offer、put,remove相关的remove()、poll()、take()方法,以及查看相关的peek()、element方法等。阻塞队列是线程安全的容器,其元素不允许为null,否则会抛出空指针异常。阻塞队列可以用于生产者消费者场景中去。

      常见的实现类有ArrayBlockingQueue以及LinkedBlockingQueue两种,下面来详细来看一下ArrayBlockingQueue以及LinkedBlockingQueue的具体实现。

  ArrayBlockingQueue重要参数及构造函数

  /** 队列中元素的数据结构 */
    final Object[] items;

    /** 队首标记,用于出队操作 */
    int takeIndex;

    /** 队尾标记,用于入队操作 */
    int putIndex;

    /** 队列中元素的个数 */
    int count;

    /*
     * Concurrency control uses the classic two-condition algorithm
     * found in any textbook.
     */

    /**对于任何操作都需要加锁,这里是一个可重入锁 */
    final ReentrantLock lock;

    /** 用于通知取元素的条件 */
    private final Condition notEmpty;

    /** 控制放元素的条件 */
    private final Condition notFull;
  /**
     * 创造一个给定容量的非公平锁的阻塞队列
     */
    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入队操作

      以常用的add方法为例子,在这个方法内部实际上是调用了offer方法,而offer方法在加锁的基础之上调用了enqueue方法来将元素放在数组的尾部,并唤醒那些阻塞了的取元素方法。

add、offer、put的主要区别如下:

add方法在成功是返回true,如果失败就抛出异常。

offer方法在添加成功返回true,添加失败返回false。

put方法如果添加不成功就会一直等待。不会出现丢节点的情况一般。

 /**
     * 将元素插入队列中,如果成功则返回true,否则的话抛出异常。可以看出其本身还是调用了offer方法。
     *
     * <p>This implementation returns <tt>true</tt> if <tt>offer</tt> succeeds,
     * else throws an <tt>IllegalStateException</tt>.
     *
     *  
     */
    public boolean add(E e) {
        if (offer(e))
            return true;
        else
            throw new IllegalStateException("Queue full");
    }
 /**
     * Inserts the specified element at the tail of this queue if it is
     * possible to do so immediately without exceeding the queue's capacity,
     * returning {@code true} upon success and {@code false} if this queue
     * is full.  This method is generally preferable to method {@link #add},
     * which can fail to insert an element only by throwing an exception.
     *
     * @throws NullPointerException if the specified element is null
     */
    public boolean offer(E e) {
        checkNotNull(e);//判断是否为空,如果元素为null,则抛出异常
        final ReentrantLock lock = this.lock;
        lock.lock();
        try {
            if (count == items.length)
                return false;
            else {
                enqueue(e);
                return true;
            }
        } finally {
            lock.unlock();
        }
    }
 /**
     * 将元素插入数组尾部,并唤醒等待取元素的线程。
     */
    private void enqueue(E x) {
        // assert lock.getHoldCount() == 1;
        // assert items[putIndex] == null;
        final Object[] items = this.items;
        items[putIndex] = x;
        if (++putIndex == items.length)
            putIndex = 0;
        count++;
        notEmpty.signal();
    }
/**
     *向数组中插入元素,如果没有空间就等待。
     *
     * @throws InterruptedException {@inheritDoc}
     * @throws NullPointerException {@inheritDoc}
     */
    public void put(E e) throws InterruptedException {
        checkNotNull(e);
        final ReentrantLock lock = this.lock;
        lock.lockInterruptibly();
        try {
            while (count == items.length)
                notFull.await();
            enqueue(e);
        } finally {
            lock.unlock();
        }
    }

ArrayBlockingQueue出队操作

    出队操作也是要加锁的,以remove为例,从头开始遍历,一直到尾部标记的地方为止,当找到一个和所给元素相等的元素时,删除这个节点的元素。至于take方法和poll方法都是删除头部元素,区别在于take方法会等待,而poll方法如果没有元素可取则会直接返回null。

 /**
     * Removes a single instance of the specified element from this queue,
     * if it is present.  More formally, removes an element {@code e} such
     * that {@code o.equals(e)}, if this queue contains one or more such
     * elements.
     * Returns {@code true} if this queue contained the specified element
     * (or equivalently, if this queue changed as a result of the call).
     *
     * <p>Removal of interior elements in circular array based queues
     * is an intrinsically slow and disruptive operation, so should
     * be undertaken only in exceptional circumstances, ideally
     * only when the queue is known not to be accessible by other
     * threads.
     *
     * @param o element to be removed from this queue, if present
     * @return {@code true} if this queue changed as a result of the call
     */
    public boolean remove(Object o) {
        if (o == null) return false;
        final Object[] items = this.items;
        final ReentrantLock lock = this.lock;
        lock.lock();
        try {
            if (count > 0) {
                final int putIndex = this.putIndex;
                int i = takeIndex;
                do {
                    if (o.equals(items[i])) {
                        removeAt(i);
                        return true;
                    }
                    if (++i == items.length)
                        i = 0;
                } while (i != putIndex);
            }
            return false;
        } finally {
            lock.unlock();
        }
    }
 public E take() throws InterruptedException {
        final ReentrantLock lock = this.lock;
        lock.lockInterruptibly();
        try {
            while (count == 0)//当元素为0时,会自动阻塞到条件队列中去。知道被其他方法唤醒。
                notEmpty.await();
            return dequeue();
        } finally {
            lock.unlock();
        }
    }

 /**
     * Extracts element at current take position, advances, and signals.
     * Call only when holding lock.
     */
    private E dequeue() {
        // assert lock.getHoldCount() == 1;
        // assert items[takeIndex] != null;
        final Object[] items = this.items;
        @SuppressWarnings("unchecked")
        E x = (E) items[takeIndex];
        items[takeIndex] = null;
        if (++takeIndex == items.length)
            takeIndex = 0;
        count--;
        if (itrs != null)
            itrs.elementDequeued();
        notFull.signal();
        return x;
    }

ArrayBlockingQueue总结

       ArrayBlockingQueue本质上就是对一个数组进行操作,它严格不允许元素为null,但是这些操作都是加了锁的操作,因此它们都是线程安全的,并且可以根据实际情况来选择锁的公平非公平,公平锁可以严格保证线程不会饥饿,而非公平锁则可以提高系统的吞吐量。并且由于它还是一个队列,对于队列的操作也大多数都是在头部或者是尾部的操作。除了锁之外,ArrayBlockingQueue还提供了两个condition来实现等待操作,这里的方法其实就是把那些被阻塞的线程放在Condition队列中,然后当有signal操作是就唤醒最前面的线程执行。整体而言这些操作就是在数组的基础之上加锁,相对简单。




LinkedBlockingQueue源码分析

     类似与ArrayList和LinkedList,和ArrayBlockingQueue对应的是LinkedBlockingqueue,不同的地方在于一个底层是数组实现,一个底层是当链表来实现。再就是一个底层只有一把锁,而另一个有两把锁。首先来看一下其中的重要参数。

   LinkedBlockingQueue有两把锁,对应着队尾和队头的操作,也就是说添加和删除操作不互斥,这和上面的是不一样的,因此其并发量要高于ArrayBlockingQueue。

 /**
     * 这里的节点相对简单,单向链表。
     */
    static class Node<E> {
        E item;

        /**
         * One of:
         * - the real successor Node
         * - this Node, meaning the successor is head.next
         * - null, meaning there is no successor (this is the last node)
         */
        Node<E> next;

        Node(E x) { item = x; }
    }

    /** 记录链表容量 */
    private final int capacity;

    /**元素数目 */
    private final AtomicInteger count = new AtomicInteger();

    /**
     * 头结点
     */
    transient Node<E> head;

    /**
     * 尾节点
     */
    private transient Node<E> last;

    /** 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();

LinkedBlockingQueue入队操作

      以offer方法为例子,当我们尝试入队一个null时就会抛出异常。其他情况下当容量不够时就返回false,否则就可以给这个入队操作进行加锁,当元素个数小于容量时就添加节点到尾部然后给count+1,唤醒其他被阻塞的添加元素线程。这里获取的count是还没有加一之前的值,因此它的值如果为0,那么至少队列中还是有一个元素的,可以唤醒消费线程消费了。

  /**
     * Inserts the specified element at the tail of this queue if it is
     * possible to do so immediately without exceeding the queue's capacity,
     * returning {@code true} upon success and {@code false} if this queue
     * is full.
     * When using a capacity-restricted queue, this method is generally
     * preferable to method {@link BlockingQueue#add add}, which can fail to
     * insert an element only by throwing an exception.
     *
     * @throws NullPointerException if the specified element is null
     */
    public boolean offer(E e) {
        if (e == null) throw new NullPointerException();//为null抛出异常
        final AtomicInteger count = this.count;
        if (count.get() == capacity)  //  量不足返回false.
            return false;  
        int c = -1; 
        Node<E> node = new Node<E>(e);
        final ReentrantLock putLock = this.putLock;
        putLock.lock();//获取put锁
        try {
            if (count.get() < capacity) {//count小于容量在队尾入队
                enqueue(node);
                c = count.getAndIncrement();//count加一
                if (c + 1 < capacity)
                    notFull.signal();//仍然有剩余容量,唤醒等待的put线程
            }
        } finally {
            putLock.unlock();
        }
        if (c == 0)
            signalNotEmpty();//唤醒消费线程
        return c >= 0;
    }
 /**
     * 这个方法意味这last.next=node,也就是把node作为last的下一个节点。
     *然后将last=last.next,也就是把last向后移。
     * @param node the node
     */
    private void enqueue(Node<E> node) {
        // assert putLock.isHeldByCurrentThread();
        // assert last.next == null;
        last = last.next = node;
    }
 /**
     * Signals a waiting take. Called only from put/offer (which do not
     * otherwise ordinarily lock takeLock.)
     */
    private void signalNotEmpty() {
        final ReentrantLock takeLock = this.takeLock;
        takeLock.lock();
        try {
            notEmpty.signal();
        } finally {
            takeLock.unlock();
        }
    }

LinkedBlockingQueue出队操作

     先来看一下remove方法的出队操作。由于remove方法要删除的元素不固定,可能出现在队列中的任何地方,因此需要同时锁住队首和队尾两把锁,然后从头到尾诸葛遍历元素,当找到元素之后就删除这个元素。这里其实类似于单链表中的节点删除,不同的地方在于要加锁!

 /**
     * Removes a single instance of the specified element from this queue,
     * if it is present.  More formally, removes an element {@code e} such
     * that {@code o.equals(e)}, if this queue contains one or more such
     * elements.
     * Returns {@code true} if this queue contained the specified element
     * (or equivalently, if this queue changed as a result of the call).
     *
     * @param o element to be removed from this queue, if present
     * @return {@code true} if this queue changed as a result of the call
     */
    public boolean remove(Object o) {
        if (o == null) return false;
        fullyLock();
        try {
            for (Node<E> trail = head, p = trail.next;
                 p != null;
                 trail = p, p = p.next) {
                if (o.equals(p.item)) {
                    unlink(p, trail);
                    return true;
                }
            }
            return false;
        } finally {
            fullyUnlock();
        }
    }
   poll 方法返回队头元素并删除之。如果队列中没有数据就返回null,否则的话就说明有元素,那么就可以讲一个元素出列,同时将count值减一,C>1意味着队列中至少有一个元素,因此可以唤醒等待着的消费线程进行消费。当c的值等于容量时,此时c的实际值是容量减一,可以唤醒等待添加元素的线程进行添加。因为他们之前最有可能会被阻塞。
 public E poll() {
        final AtomicInteger count = this.count;
        if (count.get() == 0)
            return null;
        E x = null;
        int c = -1;
        final ReentrantLock takeLock = this.takeLock;
        takeLock.lock();
        try {
            if (count.get() > 0) {
                x = dequeue();
                c = count.getAndDecrement();//获得count值并将其减一
                if (c > 1)
                    notEmpty.signal();//至少有一个元素,唤醒等待着的消费线程。
            }
        } finally {
            takeLock.unlock();
        }
        if (c == capacity)
            signalNotFull();唤醒
        return x;
    }

LinkedBlockingQueue总结

     LinkedBlockingQueue本质上是一个链表,区别于普通链表的地方在于它在队首和队尾的地方加了两把锁,分别对应于生产元素和消费元素,因此和独占锁比起来会快一些(毕竟可以首尾同时操作),它本身的元素也是不允许为null的。


ArrayBlockingQueue和LinkedBlockingQueue比较

1.ABQ的底层是数组实现,LBQ的底层是链表实现。

2.ABQ加锁只加一把,并且是全局的,而LBQ的锁有两把,分别对应着队首和队尾,同时也有两个Condition队列。也就是说,ABQ的取元素和放元素是互斥的,而LBQ则没有相互关联,因此就并发性而言,LBQ要优于ABQ。

3.ABQ 的容量是必须需要的,并且不可以扩容,而LBQ的大小可以指定,也可以不指定,不指定的情况下其值为最大整数值。

4.ABQ 支持公平锁和非公平锁,而LBQ不可指定,其本身内部使用的也是非公平锁。




参考资料:

http://cmsblogs.com/?p=2381

https://blog.csdn.net/u014082714/article/details/52215130

https://blog.csdn.net/u010412719/article/details/52337471

https://blog.csdn.net/javazejian/article/details/77410889?locationNum=1&fps=1#arrayblockingqueue的阻塞添加的实现原理

https://blog.csdn.net/u010887744/article/details/73010691


猜你喜欢

转载自blog.csdn.net/pb_yan/article/details/80656641
今日推荐