阻塞队列

阻塞队列(BlockingQueue)与普通队列(Queue)的区别主要是:

  1. 阻塞队列通过在入队和出队时加锁,保证了队列的线程安全。
  2. 阻塞队列支持阻塞添加和阻塞删除元素。

什么是阻塞添加和阻塞删除?

  • 阻塞添加:是指当队列已满时,会阻塞添加元素的线程,直到队列不满时才重新唤醒线程执行添加操作。
  • 阻塞删除:是指在队列为空时,会阻塞删除元素的线程,直到队列不为空才重新唤醒线程执行删除操作(一般会返回被删除的元素)。

阻塞队列的这种特性很适合运用在生产者-消费者模型中,生产者和消费者可以分别由多个线程组成,生产者往阻塞队列中添加元素,消费者往阻塞队列中移除元素,二者互不影响,实现了解耦。

我们常用的ArrayBlockingQueue和LinkedBlockingQueue都在JDK的java.util.concurrent包下,它们均实现了BlockingQueue接口,BlockingQueue接口代码如下(本文源码基于JDK 1.8):

java 复制代码
public interface BlockingQueue<E> extends Queue<E> {
    /**
     * Inserts the specified element into this queue if it is possible to do
     * so immediately without violating capacity restrictions, returning
     * {@code true} upon success and throwing an
     * {@code IllegalStateException} if no space is currently available.
     * When using a capacity-restricted queue, it is generally preferable to
     * use {@link #offer(Object) offer}.
     * 把元素立即插入队列,如果队列容量够用,成功返回true;如果容量不够,抛出IllegalStateException异常。
     * 当使用有容量限制的队列时,更推荐你用offer()方法添加元素
     */
    boolean add(E e);

    /**
     * Inserts the specified element into this queue if it is possible to do
     * so immediately without violating capacity restrictions, returning
     * {@code true} upon success and {@code false} if no space is currently
     * available.  When using a capacity-restricted queue, this method is
     * generally preferable to {@link #add}, which can fail to insert an
     * element only by throwing an exception.
     * 把元素立即插入队列,如果队列容量够用,成功返回true;如果容量不够,返回false。
     */
    boolean offer(E e);

    /**
     * Inserts the specified element into this queue, waiting if necessary
     * for space to become available.
     * 把元素插入队列,如果队列满了会一直等待(阻塞)
     */
    void put(E e) throws InterruptedException;

    /**
     * Inserts the specified element into this queue, waiting up to the
     * specified wait time if necessary for space to become available.
     * 把元素插入队列,如果队列已经满了,最多等待指定的时间,该方法可中断
     */
    boolean offer(E e, long timeout, TimeUnit unit)
        throws InterruptedException;

    /**
     * Retrieves and removes the head of this queue, waiting if necessary
     * until an element becomes available.
     * 获取并移除队头的元素,如果没有元素则等待(阻塞),直到有元素将唤醒等待的线程
     */
    E take() throws InterruptedException;

    /**
     * Retrieves and removes the head of this queue, waiting up to the
     * specified wait time if necessary for an element to become available.
     * 获取并移除队头的元素,在指定的等待时间前一直等待获取元素,超过时间方法将结束
     */
    E poll(long timeout, TimeUnit unit)
        throws InterruptedException;


    /**
     * 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).
     * 从队列中移除一个与指定元素equal的元素
     */
    boolean remove(Object o);

    /**
     * Returns {@code true} if this queue contains the specified element.
     * More formally, returns {@code true} if and only if this queue contains
     * at least one element {@code e} such that {@code o.equals(e)}.
     * 判断队列中是否有与指定元素equal的元素
     */
    public boolean contains(Object o);

}

BlockingQueue接口的父接口Queue中还有几个方法需要关注:

java 复制代码
public interface Queue<E> extends Collection<E> {
    /**
     * Retrieves and removes the head of this queue,
     * or returns {@code null} if this queue is empty.
     *
     * 获取并移除队列的头元素,存在就返回该元素,不存在返回null
     */
    E poll();

    /**
     * Retrieves, but does not remove, the head of this queue.  This method
     * differs from {@link #peek peek} only in that it throws an exception
     * if this queue is empty.
     * 获取队列的头元素(不移除),没有会抛异常NoSuchElementException
     */
    E element();

    /**
     * Retrieves, but does not remove, the head of this queue,
     * or returns {@code null} if this queue is empty.
     * 获取队列的头元素(不移除),存在就返回该元素,不存在返回null
     */
    E peek();
}

下面我们把实现了BlockingQueue接口的类所包含的所有方法归纳整理一下:

  1. 添加元素
  • boolean add(E e):队列没满返回true,队列满了抛出IllegalStateException异常。
  • boolean offer(E e):队列没满返回true,队列满了返回false。
  • void put(E e):无返回值,队列满了会一直阻塞,该方法可中断。
  1. 删除元素
  • boolean remove(Object o):移除指定元素,成功返回true,失败返回false。
  • E poll():获取并移除队头的元素,有元素就返回该元素,没有返回null。
  • E take():获取并移除队头的元素,没有元素会一直阻塞,该方法可中断。
  1. 获取元素
  • E element():获取队头的元素(不移除),没有会抛异常。
  • E peek():获取队头的元素(不移除),有就返回该元素,没有返回null。

阻塞队列中对元素的添加、删除和获取主要就是通过上述3类方法来实现的,需要注意的是poll()和take()方法也可以获取元素,只不过它们在获取的同时会删除。

下面我们来分析BlockingQueue的两个实现类ArrayBlockingQueue和LinkedBlockingQueue的简单使用和实现原理。

ArrayBlockingQueue的基本使用

下面我们通过ArrayBlockingQueue实现一个生产者消费者的案例,通过该案例简单了解其使用方式。

java 复制代码
public class ArrayBlockingQueueDemo {
    private final static ArrayBlockingQueue<Apple> queue= new ArrayBlockingQueue<>(1);
    public static void main(String[] args){
        new Thread(new Producer(queue)).start();
        new Thread(new Producer(queue)).start();
        new Thread(new Consumer(queue)).start();
        new Thread(new Consumer(queue)).start();
    }
}

 class Apple {
    public Apple(){
    }
 }

/**
 * 生产者线程
 */
class Producer implements Runnable{
    private final ArrayBlockingQueue<Apple> mAbq;
    Producer(ArrayBlockingQueue<Apple> arrayBlockingQueue){
        this.mAbq = arrayBlockingQueue;
    }

    @Override
    public void run() {
        while (true) {
            Produce();
        }
    }

    private void Produce(){
        try {
            Apple apple = new Apple();
            mAbq.put(apple);//队列满则阻塞
            System.out.println("生产:"+apple);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
}

/**
 * 消费者线程
 */
class Consumer implements Runnable{

    private ArrayBlockingQueue<Apple> mAbq;
    Consumer(ArrayBlockingQueue<Apple> arrayBlockingQueue){
        this.mAbq = arrayBlockingQueue;
    }

    @Override
    public void run() {
        while (true){
            try {
                TimeUnit.MILLISECONDS.sleep(1000);
                comsume();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }

    private void comsume() throws InterruptedException {
        Apple apple = mAbq.take();//队列空则阻塞
        System.out.println("消费Apple="+apple);
    }
}

代码比较简单,生产者(Producer)和消费者(Consumer)通过ArrayBlockingQueue 队列获取和添加元素,其中消费者调用了take()方法获取元素,当队列没有元素就阻塞,生产者调用put()方法添加元素,当队列满时就阻塞。通过这种方式便实现生产者消费者模式,比直接使用等待唤醒机制或者Condition条件队列更加简单。执行代码,部分打印Log如下:

java 复制代码
生产:com.example.test.blocking_queue.Apple@70d4ed14
生产:com.example.test.blocking_queue.Apple@bcd0d75
生产:com.example.test.blocking_queue.Apple@7e130d7e
消费Apple=com.example.test.blocking_queue.Apple@bcd0d75
消费Apple=com.example.test.blocking_queue.Apple@70d4ed14
消费Apple=com.example.test.blocking_queue.Apple@7e130d7e
生产:com.example.test.blocking_queue.Apple@ff288d4
消费Apple=com.example.test.blocking_queue.Apple@ff288d4
生产:com.example.test.blocking_queue.Apple@7a2c8664
消费Apple=com.example.test.blocking_queue.Apple@7a2c8664
生产:com.example.test.blocking_queue.Apple@4af0074e
...

看到打印你是否有疑问,为什么可以连续打印3条生产信息,这里连续打印3条生产信息并不代表阻塞队列中有3个元素,打印第2条和第3条生产信息的时候其实消费者已经把阻塞队列中的元素消费掉了,只不过消费信息的打印比生产信息的打印晚一点出来而已。

ArrayBlockingQueue源码解析

ArrayBlockingQueue 是一个用数组实现的有界阻塞队列,其内部按先进先出的原则对元素进行排序,我们先来看看ArrayBlockingQueue的成员变量和构造方法:

java 复制代码
public class ArrayBlockingQueue<E> extends AbstractQueue<E> implements BlockingQueue<E>, java.io.Serializable {

    /** The queued items */
    /** 队列中的元素 */
    final Object[] items;

    /** Number of elements in the queue */
    /** 队列中元素的个数 */
    int count;

    /** items index for next take, poll, peek or remove */
    /** 下一个take, poll, peek or remove的索引 */
    int takeIndex;

    /** items index for next put, offer, or add */
    /** 下一个put, offer, or add的索引 */
    int putIndex;

    /** Main lock guarding all access */
    /** 控制并发访问的锁 */
    final ReentrantLock lock;

    /** Condition for waiting takes */
    /** notEmpty条件对象,用于通知take()方法队列已有元素,可执行获取操作 */
    private final Condition notEmpty;

    /** Condition for waiting puts */
    /** notFull条件对象,用于通知put()方法队列未满,可执行添加操作 */
    private final Condition notFull;

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

    public ArrayBlockingQueue(int capacity, boolean fair) {
        if (capacity <= 0)
            throw new IllegalArgumentException();
        //items为Object数组,容量为capacity
        this.items = new Object[capacity];
        //使用的锁为ReentrantLock,fair默认为false,即非公平锁
        lock = new ReentrantLock(fair);
        notEmpty = lock.newCondition();
        notFull =  lock.newCondition();
    }

    //这个比较少用
    public ArrayBlockingQueue(int capacity, boolean fair, Collection<? extends E> c) {
        this(capacity, fair);

        final ReentrantLock lock = this.lock;
        lock.lock(); // Lock only for visibility, not mutual exclusion
        try {
            int i = 0;
            try {
                for (E e : c) {
                    checkNotNull(e);
                    items[i++] = e;
                }
            } catch (ArrayIndexOutOfBoundsException ex) {
                throw new IllegalArgumentException();
            }
            count = i;
            putIndex = (i == capacity) ? 0 : i;
        } finally {
            lock.unlock();
        }
    }

}

ArrayBlockingQueue的内部是通过一个ReentrantLock可重入锁和两个Condition条件对象来实现阻塞的,接下来看add()方法:

java 复制代码
public class ArrayBlockingQueue<E> extends AbstractQueue<E> implements BlockingQueue<E>, java.io.Serializable {

    public boolean add(E e) {
        return super.add(e);
    }

    public boolean offer(E e) {
        checkNotNull(e);
        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;
        //把x赋值给items数组putIndex对应的下标
        items[putIndex] = x;
        //索引自增,如果已是最后一个位置,重新设置 putIndex = 0
        if (++putIndex == items.length)
            putIndex = 0;
        count++;
        //唤醒调用take()方法的线程,执行元素获取操作
        notEmpty.signal();
    }
}

add()方法直接调用了super.add()方法,即AbstractQueue的add()方法:

java 复制代码
public abstract class AbstractQueue<E> extends AbstractCollection<E> implements Queue<E> {

    public boolean add(E e) {
        if (offer(e))
            return true;
        else
            throw new IllegalStateException("Queue full");
    }

}

AbstractQueue的add()方法又调用了offer()方法,最终回到了ArrayBlockingQueue里面的offer()方法。offer()方法很简单,就是在添加元素之前加锁,添加后释放锁。如果添加的时候发现数组已经满了,返回false,如果没满,添加到数组中。

add()方法与offer()方法唯一不同的地方在于add()方法如果发现数组已经满了会抛出异常。

接下来看看put()方法:

java 复制代码
    public void put(E e) throws InterruptedException {
        checkNotNull(e);
        final ReentrantLock lock = this.lock;
        lock.lockInterruptibly(); //该方法可中断
        try {
            //当数组满了
            while (count == items.length)
                notFull.await(); //将当前调用线程挂起,添加到notFull条件链表中等待唤醒
            enqueue(e);//如果数组没有满直接添加
        } finally {
            lock.unlock();
        }
    }

put()方法是一个阻塞的方法,如果判断数组已满,那么当前线程将会被notFull条件对象挂起加到等待链表中,直到队列没有满才会唤醒。如果判断队列没有满,就直接调用enqueue()方法将元素加入到数组中。

删除元素我们先来看看poll()方法:

java 复制代码
    public E poll() {
        final ReentrantLock lock = this.lock;
        lock.lock();
        try {
            return (count == 0) ? null : dequeue();
        } finally {
            lock.unlock();
        }
    }

    private E dequeue() {
        // assert lock.getHoldCount() == 1;
        // assert items[takeIndex] != null;
        final Object[] items = this.items;
        @SuppressWarnings("unchecked")
        //获取takeIndex对应的元素
        E x = (E) items[takeIndex];
        //将items数组中takeIndex对应的位置置空
        items[takeIndex] = null;
        if (++takeIndex == items.length)
            takeIndex = 0;
        count--;//元素个数减1
        if (itrs != null)
            itrs.elementDequeued(); //更新迭代器中的元素数据
        notFull.signal(); //唤醒notFull阻塞链表中添加元素的线程
        return x; //返回对应的元素
    }

poll()方法很简单,判断如果数组元素个数为0直接返回null,否则执行dequeue()删除头元素。再来看看remove()方法:

java 复制代码
    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) {
                //获取下一个put元素的索引
                final int putIndex = this.putIndex;
                //获取下一个take元素的索引
                int i = takeIndex;
                do {
                    if (o.equals(items[i])) {
                        removeAt(i);
                        return true; //删除成功返回true
                    }
                    //当前删除索引执行加1后判断是否与数组长度相等
                    //若为true,说明索引已到数组尽头,将i设置为0
                    if (++i == items.length)
                        i = 0;
                } while (i != putIndex);
            }
            return false;
        } finally {
            lock.unlock();
        }
    }

    void removeAt(final int removeIndex) {
        // assert lock.getHoldCount() == 1;
        // assert items[removeIndex] != null;
        // assert removeIndex >= 0 && removeIndex < items.length;
        final Object[] items = this.items;
        //判断要删除的元素是否为当前队列头元素
        if (removeIndex == takeIndex) {
            // removing front item; just advance
            //如果是直接删除
            items[takeIndex] = null;
            if (++takeIndex == items.length)
                takeIndex = 0;
            count--;//队列元素个数减1
            if (itrs != null)
                itrs.elementDequeued();//更新迭代器中的数据
        } else {
            // an "interior" remove

            // slide over all others up through putIndex.
            //如果要删除的元素不在队列头部,
            //那么只需循环迭代把删除元素后面的所有元素往前移动一个位置
            final int putIndex = this.putIndex;
            for (int i = removeIndex;;) {
                //获取要删除节点索引的下一个索引
                int next = i + 1;
                if (next == items.length)
                    next = 0;
                //如果查找的索引不等于要添加元素的索引
                if (next != putIndex) {
                    items[i] = items[next]; //元素一个个往前移动
                    i = next;
                } else { //如果next==putIndex
                    items[i] = null; //元素都往前移动了一个位置,最后一个元素要置null
                    this.putIndex = i; //下一个put的元素位置为i
                    break;  //退出循环
                }
            }
            count--;
            if (itrs != null)
                itrs.removedAt(removeIndex); //更新迭代器数据
        }
        notFull.signal(); //唤醒添加线程
    }

remove()方法使用了while循环,如果发现数组中的元素与要删除的元素equal,使用removeAt()方法删除该元素并返回true,没有找到该元素则返回false。接下来看看take()方法:

java 复制代码
    public E take() throws InterruptedException {
        final ReentrantLock lock = this.lock;
        lock.lockInterruptibly();
        try {
            while (count == 0)
                notEmpty.await();
            return dequeue();
        } finally {
            lock.unlock();
        }
    }

take()方法很简单,有元素就删除,没有就阻塞,阻塞是可以中断的。如果数组中没有数据那么就加入notEmpty条件链表等待,如果有新的put线程添加了数据,put线程会唤醒take线程,执行take操作。

peek()方法直接取出数组中队头的元素,不做删除操作,代码如下:

java 复制代码
    public E peek() {
        final ReentrantLock lock = this.lock;
        lock.lock();
        try {
            return itemAt(takeIndex); // null when queue is empty
        } finally {
            lock.unlock();
        }
    }

    final E itemAt(int i) {
        return (E) items[i];
    }

LinkedBlockingQueue源码解析

先来看看LinkedBlockingQueue的成员变量和构造方法:

java 复制代码
public class LinkedBlockingQueue<E> extends AbstractQueue<E> implements BlockingQueue<E>, java.io.Serializable {

    /**
     * Linked list node class
     */
    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; }
    }

    /** The capacity bound, or Integer.MAX_VALUE if none */
    /** 容量 */ 
    private final int capacity;

    /** Current number of elements */
    /** 元素的个数 */
    private final AtomicInteger count = new AtomicInteger();

    /**
     * Head of linked list.
     * Invariant: head.item == null
     * 链表头
     */
    transient Node<E> head;

    /**
     * Tail of linked list.
     * Invariant: last.next == null
     * 链表尾
     */
    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();

    public LinkedBlockingQueue() {
        //容量默认为Integer.MAX_VALUE
        this(Integer.MAX_VALUE);
    }

    /**
     * Creates a {@code LinkedBlockingQueue} with the given (fixed) capacity.
     * 创建容量为capacity的LinkedBlockingQueue
     */
    public LinkedBlockingQueue(int capacity) {
        if (capacity <= 0) throw new IllegalArgumentException();
        this.capacity = capacity;
        last = head = new Node<E>(null);
    }

    /**
     * Creates a {@code LinkedBlockingQueue} with a capacity of
     * {@link Integer#MAX_VALUE}, initially containing the elements of the
     * given collection,
     * added in traversal order of the collection's iterator.
     * 创建容量为capacity的LinkedBlockingQueue,它包含所传集合中的元素
     */
    public LinkedBlockingQueue(Collection<? extends E> c) {
        this(Integer.MAX_VALUE);
        final ReentrantLock putLock = this.putLock;
        putLock.lock(); // Never contended, but necessary for visibility
        try {
            int n = 0;
            for (E e : c) {
                if (e == null)
                    throw new NullPointerException();
                if (n == capacity)
                    throw new IllegalStateException("Queue full");
                enqueue(new Node<E>(e));
                ++n;
            }
            count.set(n);
        } finally {
            putLock.unlock();
        }
    }
}    

源码中显示有3种方式可以构造LinkedBlockingQueue,但是更推荐手动传值的方式,避免占用过多的内存造成内存浪费。LinkedBlockingQueue的吞吐量要高于ArrayBlockingQueue,因为其内部实现添加和删除操作使用了两个ReentrantLock来控制并发,而ArrayBlockingQueue内部只是使用一个ReentrantLock。

先来看看add()方法,LinkedBlockingQueue的add()方法在其父类AbstractQueue中,代码如下:

typescript 复制代码
public boolean add(E e) {
    if (offer(e))
        return true;
    else
        throw new IllegalStateException("Queue full");
}

里面先调用了LinkedBlockingQueue的offer()方法,代码如下:

java 复制代码
public boolean offer(E e) {
    if (e == null) throw new NullPointerException();
    final AtomicInteger count = this.count;
    //判断队列是否已满
    if (count.get() == capacity)
        return false;
    //c初始赋值为-1    
    int c = -1;
    Node<E> node = new Node<E>(e);
    final ReentrantLock putLock = this.putLock;
    putLock.lock();
    try {
        //在加锁后再次判断队列是否已满,如果满了直接跳过
        if (count.get() < capacity) {
            //添加node
            enqueue(node);
            //元素个数加1
            c = count.getAndIncrement();
            //如果队列还没有满
            if (c + 1 < capacity)
                notFull.signal();//唤醒添加元素的线程
        }
    } finally {
        putLock.unlock();
    }
    //c==0表示添加元素成功了
    if (c == 0)
        signalNotEmpty();//唤醒删除元素的线程
    return c >= 0; //成功返回true,失败返回false
}

private void enqueue(Node<E> node) {
    // assert putLock.isHeldByCurrentThread();
    // assert last.next == null;
    //last指向last的下一个结点,并且last赋值为node
    last = last.next = node;
}

private void signalNotEmpty() {
    final ReentrantLock takeLock = this.takeLock;
    takeLock.lock();
    try {
        notEmpty.signal();
    } finally {
        takeLock.unlock();
    }
}

可见其add()方法与offer()方法的区别同ArrayBlockingQueue是一样的,add()方法在队列已经满了的情况下会抛出异常。ArrayBlockingQueue的offer()方法只使用了一把锁,而LinkedBlockingQueue的offer()方法中使用了2把锁,在添加元素的过程中判断队列没满还会通知添加元素的线程继续添加。接下来分析put()方法:

java 复制代码
public void put(E e) throws InterruptedException {
    if (e == null) throw new NullPointerException();
    // Note: convention in all put/take/etc is to preset local var
    // holding count negative to indicate failure unless set.
    int c = -1;
    Node<E> node = new Node<E>(e);
    final ReentrantLock putLock = this.putLock;
    final AtomicInteger count = this.count;
    putLock.lockInterruptibly();
    try {
        /*
         * Note that count is used in wait guard even though it is
         * not protected by lock. This works because count can
         * only decrease at this point (all other puts are shut
         * out by lock), and we (or some other waiting put) are
         * signalled if it ever changes from capacity. Similarly
         * for all other uses of count in other wait guards.
         */
         //判断如果队列满了,当前线程就添加到notFull链表中等待
        while (count.get() == capacity) {
            notFull.await();
        }
        //入队
        enqueue(node);
        //元素个数加1
        c = count.getAndIncrement();
        if (c + 1 < capacity)
            notFull.signal();
    } finally {
        putLock.unlock();
    }
    if (c == 0)
        signalNotEmpty();
}

put()方法与offer()方法的代码差不多,只不过put()方法发现队列满了会阻塞,所以也就没有返回值。

接下来分析LinkedBlockingQueue中删除元素的方法:

csharp 复制代码
public boolean remove(Object o) {
    if (o == null) return false;
    fullyLock();
    try {
        //初始trail指向head,p指向trail.next
        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();
    }
}

void fullyLock() {
    putLock.lock();
    takeLock.lock();
}

/**
 * Unlocks to allow both puts and takes.
 */
void fullyUnlock() {
    takeLock.unlock();
    putLock.unlock();
}

void unlink(Node<E> p, Node<E> trail) {
    // assert isFullyLocked();
    // p.next is not changed, to allow iterators that are
    // traversing p to maintain their weak-consistency guarantee.
    //结点p置空
    p.item = null;
    trail.next = p.next;
    //如果p已经到了队尾
    if (last == p)
        last = trail;//last指向队尾的前一个元素
    //判断如果执行减1之前队列是满的    
    if (count.getAndDecrement() == capacity)
        notFull.signal();//通知take线程
}

这里注意count.getAndDecrement()的返回值是count执行减1之前的值。remove()方法中同时使用了2把锁,这是因为remove()方法需要遍历链表,如果遍历的过程中执行了添加或删除元素的操作会出现同步问题。接下来看看poll()方法:

ini 复制代码
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();
            if (c > 1)
                notEmpty.signal();
        }
    } finally {
        takeLock.unlock();
    }
    if (c == capacity)
        signalNotFull();
    return x;
}

private E dequeue() {
    // assert takeLock.isHeldByCurrentThread();
    // assert head.item == null;
    //获取头结点,头结点为Node<E>(null);
    Node<E> h = head;
    //获取头结点的下一个结点(即要删除的节点,队列中的第一个实际元素)
    Node<E> first = h.next;
    h.next = h; // help GC,自己next指向自己
    head = first; //更新头结点
    E x = first.item; 
    first.item = null; //头结点item置null
    return x;
}

poll()方法与offer()方法看起来差不多,只不过一个是删除一个是添加。接下来看take()方法:

ini 复制代码
public E take() throws InterruptedException {
    E x;
    int c = -1;
    final AtomicInteger count = this.count;
    final ReentrantLock takeLock = this.takeLock;
    takeLock.lockInterruptibly();
    try {
        while (count.get() == 0) {
            notEmpty.await();
        }
        x = dequeue();
        c = count.getAndDecrement();
        if (c > 1)
            notEmpty.signal();
    } finally {
        takeLock.unlock();
    }
    if (c == capacity)
        signalNotFull();
    return x;
}

take()是一个阻塞可中断的方法,判断如果队列为空,会一直阻塞等待。LinkedBlockingQueue中直接获取头节点的方法:

kotlin 复制代码
public E peek() {
    if (count.get() == 0)
        return null;
    final ReentrantLock takeLock = this.takeLock;
    takeLock.lock();
    try {
        //获取head的下一个结点
        Node<E> first = head.next;
        if (first == null)
            return null;
        else
            return first.item;
    } finally {
        takeLock.unlock();
    }
}

好了,LinkedBlockingQueue的源码就分析到这里。

总结

ArrayBlockingQueue与LinkedBlockingQueue的区别:

  1. ArrayBlockingQueue有界,初始化必须指定大小,LinkedBlockingQueue可以是有界的也可以是无界的(Integer.MAX_VALUE),当添加速度大于移除速度时,在无界的情况下可能会造成内存溢出等问题。
  2. ArrayBlockingQueue采用的是数组作为数据的存储容器,LinkedBlockingQueue采用的是Node节点。
  3. 由于ArrayBlockingQueue采用的是数组,因此在插入或删除元素时不会产生或销毁任何额外的对象实例,而LinkedBlockingQueue则会生成一个额外的Node对象,这可能在需要高效并发地处理大批量数据的时对GC产生较大的影响。
  4. ArrayBlockingQueue的锁是没有分离的,即添加操作和移除操作采用的同一把锁,而LinkedBlockingQueue的锁是分离的,其添加采用的是putLock,移除采用的是takeLock,这样能大大提高队列的吞吐量,也意味着在高并发的情况下生产者和消费者可以并行地操作队列中的数据,以此来提高整个队列的并发性能。
相关推荐
manba_5 分钟前
leetcode-560. 和为 K 的子数组
数据结构·算法·leetcode
汉字萌萌哒43 分钟前
【2022 CCF 非专业级别软件能力认证第一轮(CSP-J1)入门级 C++语言试题及解析】
数据结构·c++·算法
th新港1 小时前
CCF201909_1
数据结构·c++·算法·ccf
Monodye1 小时前
【Java】网络编程:TCP_IP协议详解(IP协议数据报文及如何解决IPv4不够的状况)
java·网络·数据结构·算法·系统架构
pzx_0011 小时前
【内积】内积计算公式及物理意义
数据结构·python·opencv·算法·线性回归
重生之我要进大厂5 小时前
LeetCode 876
java·开发语言·数据结构·算法·leetcode
Happy鱿鱼6 小时前
C语言-数据结构 有向图拓扑排序TopologicalSort(邻接表存储)
c语言·开发语言·数据结构
KBDYD10106 小时前
C语言--结构体变量和数组的定义、初始化、赋值
c语言·开发语言·数据结构·算法
Crossoads6 小时前
【数据结构】排序算法---桶排序
c语言·开发语言·数据结构·算法·排序算法
QXH2000006 小时前
数据结构—单链表
c语言·开发语言·数据结构