Java 进阶——多线程优化之线程池 ThreadPoolExecutor的核心容器阻塞队列详解(一)

文章大纲

  • 引言
  • 一、线程池中的阻塞队列模型
  • 二、BlockingQueue 主要四种的“异常”处理逻辑
  • 三、ArrayBlockingQueue
    • 1、创建ArrayBlockingQueue
    • 2、向队列中插入元素
    • 3、获取队列中的元素
  • 四、LinkedBlockingQueue
  • 五、PriorityBlockingQueue
    • 1、创建PriorityBlockingQueue
    • 2、PriorityBlockingQueue如何实现无界
    • 3、实现优先级
  • 六、DelayQueue
    • 1、创建DelayQueue
    • 2、获取DelayQueue的元素
    • 3、插入元素到DelayQueue
    • 4、实现Delayed 接口
    • 5、实现延时阻塞队列
    • 6、DelayQueue中的Leader-follower线程模型
    • 7、DelayQueue 使用场景:
  • 七、SynchronousQueue
  • 八、LinkedTransferQueue
    • 1、transfer方法
    • 2、tryTransfer方法
  • 九、LinkedBlockingDeque
    • 小结

引言

多线程我想无论是后端开发,还是对于App开发者来说都不会陌生,何况Android强制要求不能在主线程中做网络请求,于是乎,在很多初学者或者App的源码中会出现会多的new Thread…的方式,这样的代码是不优雅而且存在很多的隐患,假如说在用户退出App之后,如果线程内的工作还未执行完毕此时是无法被回收的,更不必说如果是无限循环的线程,那么可能永远无法回收,永远占着内存和CPU资源,这是多么大的浪费,而且项目庞大了还不好维护,所以如果你还是每次new Thread的话,我建议你阅读下这篇文章或者学习下ThreadPoolExecutor线程池,了解一哈,在进入线程池的使用之前,先介绍下线程池框架中的核心容器——阻塞队列,由于这两篇文章不仅仅是简单讲解如何使用,会涉及到背后的一些设计思想和源码分析,理论知识会比较多,部分内容整理摘自《Java 并发编程的艺术》和JDK文档。

  • Java 进阶——多线程优化之线程池ThreadPoolExecutor的核心容器阻塞队列详解(一)

  • Java 进阶——多线程优化之线程池 ThreadPoolExecutor框架原理及使用详解(二)

一、线程池中的阻塞队列模型

在我们学习线程池种类之前,先得了解一下阻塞队列模型的相关知识。阻塞队列是一个支持两个附加操作的队列——在队列为空时,获取元素的线程会等待队列变为非空;而当队列满时,存储元素的线程会等待队列可用。在多线程编程过程中为了业务解耦和架构设计,经常会使用并发容器用于存储多线程间的共享数据,这样不仅可以保证线程安全,还可以简化各个线程操作。前面我们在介绍线程池的时候说过线程池机制其实本质上就是生产者–消费者模型(生产者是往阻塞队列里添加元素的线程,消费者是从阻塞队列里拿元素的线程),那么阻塞队列就相当于是模型中的容器当生产者往队列中添加元素时,如果队列已经满了,生产者所在的线程就会阻塞,直到消费者取元素时 notify 它; 消费者去队列中取元素时,如果队列中是空的,消费者所在的线程就会阻塞,直到生产者放入元素 notify 它,而所谓采取不同的策略就是基于不同的阻塞队列模型的。JDK 1.6一共提供了7种阻塞队列模式——ArrayBlockingQueueLinkedBlockingQueueDelayQueueSynchronousQueuePriorityBlockingQueue、LinkedTransferQueue、LinkedBlockingDeque,他们统一实现**java.util.concurrent.BlockingQueue< E >**泛型接口,为我们实现不同种类的线程池提供了基础。

  • ArrayBlockingQueue ——一个由数组结构组成的有界阻塞队列。
  • LinkedBlockingQueue ——一个由链表结构组成的有界阻塞队列。
  • PriorityBlockingQueue ——一个支持优先级排序无界阻塞队列。
  • DelayQueue——一个使用优先级队列实现的无界阻塞队列。
  • SynchronousQueue——一个不存储元素的阻塞队列。
  • LinkedTransferQueue——一个由链表结构组成的无界阻塞队列。
  • LinkedBlockingDeque——一个由链表结构组成的双向阻塞队列。

二、BlockingQueue 主要四种的“异常”处理逻辑

通过上面BlockingQueue 主要源码部分,可以知道当当前队列操作(添加/读取)不可达时,BlockingQueue 通常会针对不同种类的操作采取不同的处理措施:

package java.util.concurrent;

import java.util.Collection;
import java.util.Queue;
 * <p>
 * Usage example, based on a typical producer-consumer scenario.
 * Note that a {@code BlockingQueue} can safely be used with multiple
 * producers and multiple consumers.
 * <pre> {@code
 * class Producer implements Runnable {
 *   private final BlockingQueue queue;
 *   Producer(BlockingQueue q) { queue = q; }
 *   public void run() {
 *     try {
 *       while (true) { queue.put(produce()); }
 *     } catch (InterruptedException ex) { ... handle ...}
 *   }
 *   Object produce() { ... }
 * }
 *
 * class Consumer implements Runnable {
 *   private final BlockingQueue queue;
 *   Consumer(BlockingQueue q) { queue = q; }
 *   public void run() {
 *     try {
 *       while (true) { consume(queue.take()); }
 *     } catch (InterruptedException ex) { ... handle ...}
 *   }
 *   void consume(Object x) { ... }
 * }
 *
 * class Setup {
 *   void main() {
 *     BlockingQueue q = new SomeQueueImplementation();
 *     Producer p = new Producer(q);
 *     Consumer c1 = new Consumer(q);
 *     Consumer c2 = new Consumer(q);
 *     new Thread(p).start();
 *     new Thread(c1).start();
 *     new Thread(c2).start();
 *   }
 * }}</pre>
 */
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}.
     *
     * @param e the element to add
     * @return {@code true} (as specified by {@link Collection#add})
     * @throws IllegalStateException if the element cannot be added at this
     *         time due to capacity restrictions
     * @throws ClassCastException if the class of the specified element
     *         prevents it from being added to this queue
     * @throws NullPointerException if the specified element is null
     * @throws IllegalArgumentException if some property of the specified
     *         element prevents it from being added to this queue
     */
    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.
     *
     * @param e the element to add
     * @return {@code true} if the element was added to this queue, else
     *         {@code false}
     * @throws ClassCastException if the class of the specified element
     *         prevents it from being added to this queue
     * @throws NullPointerException if the specified element is null
     * @throws IllegalArgumentException if some property of the specified
     *         element prevents it from being added to this queue
     */
    boolean offer(E e);

    /**
     * Inserts the specified element into this queue, waiting if necessary
     * for space to become available.
     *
     * @param e the element to add
     * @throws InterruptedException if interrupted while waiting
     * @throws ClassCastException if the class of the specified element
     *         prevents it from being added to this queue
     * @throws NullPointerException if the specified element is null
     * @throws IllegalArgumentException if some property of the specified
     *         element prevents it from being added to this queue
     */
    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.
     *
     * @param e the element to add
     * @param timeout how long to wait before giving up, in units of
     *        {@code unit}
     * @param unit a {@code TimeUnit} determining how to interpret the
     *        {@code timeout} parameter
     * @return {@code true} if successful, or {@code false} if
     *         the specified waiting time elapses before space is available
     * @throws InterruptedException if interrupted while waiting
     * @throws ClassCastException if the class of the specified element
     *         prevents it from being added to this queue
     * @throws NullPointerException if the specified element is null
     * @throws IllegalArgumentException if some property of the specified
     *         element prevents it from being added to this queue
     */
    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.
     *
     * @return the head of this queue
     * @throws InterruptedException if interrupted while waiting
     */
    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.
     *
     * @param timeout how long to wait before giving up, in units of
     *        {@code unit}
     * @param unit a {@code TimeUnit} determining how to interpret the
     *        {@code timeout} parameter
     * @return the head of this queue, or {@code null} if the
     *         specified waiting time elapses before an element is available
     * @throws InterruptedException if interrupted while waiting
     */
    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).
     *
     * @param o element to be removed from this queue, if present
     * @return {@code true} if this queue changed as a result of the call
     * @throws ClassCastException if the class of the specified element
     *         is incompatible with this queue
     * (optional)
     * @throws NullPointerException if the specified element is null
     * (optional)
     */
    boolean remove(Object o);
	
}

方法\处理方式 抛出异常 返回特殊值 一直阻塞 超时退出
插入方法 add(e) offer(e) put(e) offer(e,time,unit)
移除方法 remove() poll() take() poll(time,unit)
检查方法 element() peek() 不可用 不可用
  • 当执行add(), remove(), element()不可达时,抛出异常 ,当阻塞队列满时候,再往队列里插入元素,会抛出IllegalStateException(“Queue full”)异常;当队列为空时,从队列里获取元素时会抛出NoSuchElementException异常

  • 当执行offer(), poll(), peek()返回特殊值(插入方法会返回布尔值;移除方法则是从队列里拿出一个元素,如果没有则返回null)

  • 当执行put(), take()不可达时,一直阻塞当前线程,直到操作可以进行 ,当阻塞队列满时,如果生产者线程往队列里put元素,队列会一直阻塞生产者线程,直到拿到数据,或者响应中断退出。当队列空时,消费者线程试图从队列里take元素,队列也会阻塞消费者线程,直到队列可用。

  • 当执行offer(), poll()不可达时,阻塞一段时间,超时后退出 (当阻塞队列满\为空时,队列会阻塞生产者\消费者线程一段时间,如果超过一定的时间,线程就会退出)

总之,BlockingQueue 中不允许有 null 元素,因此在 add(), offer(), put() 时如果参数是 null,会抛出空指针,null 是用来有异常情况时做返回值的。

注意:如果是无界阻塞队列,队列不可能会出现满的情况,所以使用put或offer方法永远不会被阻塞,而且使用offer方法时,该方法永远返回true。

三、ArrayBlockingQueue

ArrayBlockingQueue 是一个内部使用数组实现的有界队列,一旦创建后,容量不可变(因为数组不可变长)。队列中的元素按 FIFO 原则进行排序,每次队列头部读取元素,并插入元素到尾部。默认ArrayBlockingQueue 不保证线程公平的访问队列(公平访问队列是指阻塞的所有生产者线程或消费者线程,当队列可用时,可以按照阻塞的先后顺序访问队列,即先阻塞的生产者线程,可以先往队列里插入元素,先阻塞的消费者线程,可以先从队列里获取元素)但是也正因为不公平所以从一定程度上提高了吞吐量(非公平性是对先等待的线程是非公平的,当队列可用时,阻塞的线程都可以争夺访问队列的资格,有可能先阻塞的线程最后才访问队列)。

**注意:只是默认是不保证线程公平的访问队列并非不能支持,当然也可以通过对应的构造方法new ArrayBlockingQueue(100,true)来构造公平的阻塞队列

package java.util.concurrent;

import java.lang.ref.WeakReference;
import java.util.AbstractQueue;
import java.util.Arrays;
import java.util.Collection;
import java.util.Iterator;
import java.util.NoSuchElementException;
import java.util.Objects;
import java.util.Spliterator;
import java.util.Spliterators;
import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.ReentrantLock;

/**
 * A bounded {@linkplain BlockingQueue blocking queue} backed by an
 * array.  This queue orders elements FIFO (first-in-first-out).  The
 * head of the queue is that element that has been on the
 * queue the longest time.  The tail of the queue is that
 * element that has been on the queue the shortest time. New elements
 * are inserted at the tail of the queue, and the queue retrieval
 * operations obtain elements at the head of the queue.
 *
 * 

This is a classic "bounded buffer", in which a * fixed-sized array holds elements inserted by producers and * extracted by consumers. Once created, the capacity cannot be * changed. Attempts to {@code put} an element into a full queue * will result in the operation blocking; attempts to {@code take} an * element from an empty queue will similarly block. * *

This class supports an optional fairness policy for ordering * waiting producer and consumer threads. By default, this ordering * is not guaranteed. However, a queue constructed with fairness set * to {@code true} grants threads access in FIFO order. Fairness * generally decreases throughput but reduces variability and avoids * starvation. * *

This class and its iterator implement all of the * optional methods of the {@link Collection} and {@link * Iterator} interfaces. * @param the type of elements held in this queue */ public class ArrayBlockingQueue<E> extends AbstractQueue<E> implements BlockingQueue<E>, java.io.Serializable { private static final long serialVersionUID = -817911632652898426L; /** The queued items 保存队列元素的数组*/ final Object[] items; /** items index for next take, poll, peek or remove 下一次读取元素的索引 */ int takeIndex; /** items index for next put, offer, or add 下一次插入元素的索引*/ int putIndex; /** Number of elements in the queue 当前队列中元素总数*/ int count; /** Main lock guarding all access 所有操作的重入锁,实现的访问公平性,两个 Condition 保证了插入和读取元素的并发控制*/ final ReentrantLock lock; /** Condition for waiting takes */ private final Condition notEmpty; /** Condition for waiting puts */ private final Condition notFull; /** * Circularly decrements array index i. */ final int dec(int i) { return ((i == 0) ? items.length : i) - 1; } /** * Returns item at index i. */ @SuppressWarnings("unchecked") final E itemAt(int i) { return (E) items[i]; } /** * Extracts element at current take position, advances, and signals. * Call only when holding lock. */ private E dequeue() { 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; } /** * Deletes item at array index removeIndex. * Utility for remove(Object) and iterator.remove. * Call only when holding lock. */ 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--; if (itrs != null) itrs.elementDequeued(); } else { // an "interior" remove // slide over all others up through putIndex. for (int i = removeIndex, putIndex = this.putIndex;;) { int pred = i; if (++i == items.length) i = 0; if (i == putIndex) { items[pred] = null; this.putIndex = pred; break; } items[pred] = items[i]; } count--; if (itrs != null) itrs.removedAt(removeIndex); } notFull.signal(); } /** * Creates an {@code ArrayBlockingQueue} with the given (fixed) * capacity and default access policy. * @param capacity the capacity of this queue * @throws IllegalArgumentException if {@code capacity < 1} */ public ArrayBlockingQueue(int capacity) { this(capacity, false); } /** * Creates an {@code ArrayBlockingQueue} with the given (fixed) * capacity and the specified access policy. * * @param capacity the capacity of this queue * @param fair if {@code true} then queue accesses for threads blocked * on insertion or removal, are processed in FIFO order; * if {@code false} the access order is unspecified. * @throws IllegalArgumentException if {@code capacity < 1} * 根据指定的容量创建一个Object数组,并初始化重入锁和两个并发控制条件 */ 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(); } /** * Creates an {@code ArrayBlockingQueue} with the given (fixed) * capacity, the specified access policy and initially containing the * elements of the given collection, * added in traversal order of the collection's iterator. * * @param capacity the capacity of this queue * @param fair if {@code true} then queue accesses for threads blocked * on insertion or removal, are processed in FIFO order; * if {@code false} the access order is unspecified. * @param c the collection of elements to initially contain * @throws IllegalArgumentException if {@code capacity} is less than * {@code c.size()}, or less than 1. * @throws NullPointerException if the specified collection or any * of its elements are null */ 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) items[i++] = Objects.requireNonNull(e); } catch (ArrayIndexOutOfBoundsException ex) { throw new IllegalArgumentException(); } count = i; putIndex = (i == capacity) ? 0 : i; } finally { lock.unlock(); } } /** * 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 throwing an * {@code IllegalStateException} if this queue is full. * @param e the element to add * @return {@code true} (as specified by {@link Collection#add}) * @throws IllegalStateException if this queue is full * @throws NullPointerException if the specified element is null * 如果没有超过队列的容量,直接在队尾处插入指定元素e,add(E) 调用了父类的方法,而父类里没有实现 offer(E),本质上还是调用自身的offer(E),如果返回 false 就抛出异常。 */ public boolean add(E e) { return super.add(e); } /** * 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) { Objects.requireNonNull(e); final ReentrantLock lock = this.lock; lock.lock(); try { if (count == items.length) return false; else { enqueue(e); return true; } } finally { lock.unlock(); } } /** * Inserts the specified element at the tail of this queue, waiting * up to the specified wait time for space to become available if * the queue is full. */ public boolean offer(E e, long timeout, TimeUnit unit) throws InterruptedException { Objects.requireNonNull(e); long nanos = unit.toNanos(timeout); final ReentrantLock lock = this.lock; lock.lockInterruptibly(); try { while (count == items.length) { if (nanos <= 0L) return false; nanos = notFull.awaitNanos(nanos); } enqueue(e); return true; } finally { lock.unlock(); } } /** * Inserts element at current put position, advances, and signals.Call only when holding lock. * enqueue(E) 方法会将元素添加到数组队列尾部,如果添加元素后队列满了,就修改 putIndex 为 0 ,添加后调用 notEmpty.signal() 通知唤醒阻塞在获取元素的线程 */ private void enqueue(E x) { final Object[] items = this.items; items[putIndex] = x; if (++putIndex == items.length) putIndex = 0; count++; notEmpty.signal(); } /** * Inserts the specified element at the tail of this queue, waiting * for space to become available if the queue is full. * put() 方法可以响应中断,当队列满了,就调用 notFull.await() 阻塞等待,等有消费者获取元素后继续执行; 可以添加时还是调用 enqueue(E)。 */ public void put(E e) throws InterruptedException { Objects.requireNonNull(e); final ReentrantLock lock = this.lock; lock.lockInterruptibly(); try { while (count == items.length) notFull.await(); enqueue(e); } finally { lock.unlock(); } } public E poll() { final ReentrantLock lock = this.lock; lock.lock(); try { return (count == 0) ? null : dequeue(); } finally { lock.unlock(); } } public E take() throws InterruptedException { final ReentrantLock lock = this.lock; lock.lockInterruptibly(); try { while (count == 0) notEmpty.await(); return dequeue(); } finally { lock.unlock(); } } //take() 方法可以响应中断,与 poll() 不同的是,如果队列中没有数据会一直阻塞等待,直到中断或者有元素,有元素时还是调用 dequeue() 方法。 public E poll(long timeout, TimeUnit unit) throws InterruptedException { long nanos = unit.toNanos(timeout); final ReentrantLock lock = this.lock; lock.lockInterruptibly(); try { while (count == 0) { if (nanos <= 0L) return null; nanos = notEmpty.awaitNanos(nanos); } return dequeue(); } finally { lock.unlock(); } } //直接返回数组中队尾的元素,并不会删除元素。如果队列中没有元素返回的是 null public E peek() { final ReentrantLock lock = this.lock; lock.lock(); try { return itemAt(takeIndex); // null when queue is empty } finally { lock.unlock(); } } /** * Returns the number of elements in this queue. */ public int size() { final ReentrantLock lock = this.lock; lock.lock(); try { return count; } finally { lock.unlock(); } } /** * Returns the number of additional elements that this queue can ideally * (in the absence of memory or resource constraints) accept without * blocking. This is always equal to the initial capacity of this queue * less the current {@code size} of this queue. * *

Note that you cannot always tell if an attempt to insert * an element will succeed by inspecting {@code remainingCapacity} * because it may be the case that another thread is about to * insert or remove an element. */ public int remainingCapacity() { final ReentrantLock lock = this.lock; lock.lock(); try { return items.length - count; } finally { lock.unlock(); } } /** * 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). * *

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 ReentrantLock lock = this.lock; lock.lock(); try { if (count > 0) { final Object[] items = this.items; 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(); } } /** * 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)}. * * @param o object to be checked for containment in this queue * @return {@code true} if this queue contains the specified element */ public boolean contains(Object o) { if (o == null) return false; final ReentrantLock lock = this.lock; lock.lock(); try { if (count > 0) { final Object[] items = this.items; final int putIndex = this.putIndex; int i = takeIndex; do { if (o.equals(items[i])) return true; if (++i == items.length) i = 0; } while (i != putIndex); } return false; } finally { lock.unlock(); } } /** * Returns an array containing all of the elements in this queue, in * proper sequence. * *

The returned array will be "safe" in that no references to it are * maintained by this queue. (In other words, this method must allocate * a new array). The caller is thus free to modify the returned array. * *

This method acts as bridge between array-based and collection-based * APIs. * * @return an array containing all of the elements in this queue */ public Object[] toArray() { final ReentrantLock lock = this.lock; lock.lock(); try { final Object[] items = this.items; final int end = takeIndex + count; final Object[] a = Arrays.copyOfRange(items, takeIndex, end); if (end != putIndex) System.arraycopy(items, 0, a, items.length - takeIndex, putIndex); return a; } finally { lock.unlock(); } } /** * Returns an array containing all of the elements in this queue, in * proper sequence; the runtime type of the returned array is that of * the specified array. If the queue fits in the specified array, it * is returned therein. Otherwise, a new array is allocated with the * runtime type of the specified array and the size of this queue. * *

If this queue fits in the specified array with room to spare * (i.e., the array has more elements than this queue), the element in * the array immediately following the end of the queue is set to * {@code null}. * *

Like the {@link #toArray()} method, this method acts as bridge between * array-based and collection-based APIs. Further, this method allows * precise control over the runtime type of the output array, and may, * under certain circumstances, be used to save allocation costs. * *

Suppose {@code x} is a queue known to contain only strings. * The following code can be used to dump the queue into a newly * allocated array of {@code String}: * *

 {@code String[] y = x.toArray(new String[0]);}
* * Note that {@code toArray(new Object[0])} is identical in function to * {@code toArray()}. * * @param a the array into which the elements of the queue are to * be stored, if it is big enough; otherwise, a new array of the * same runtime type is allocated for this purpose * @return an array containing all of the elements in this queue * @throws ArrayStoreException if the runtime type of the specified array * is not a supertype of the runtime type of every element in * this queue * @throws NullPointerException if the specified array is null */
@SuppressWarnings("unchecked") public <T> T[] toArray(T[] a) { final ReentrantLock lock = this.lock; lock.lock(); try { final Object[] items = this.items; final int count = this.count; final int firstLeg = Math.min(items.length - takeIndex, count); if (a.length < count) { a = (T[]) Arrays.copyOfRange(items, takeIndex, takeIndex + count, a.getClass()); } else { System.arraycopy(items, takeIndex, a, 0, firstLeg); if (a.length > count) a[count] = null; } if (firstLeg < count) System.arraycopy(items, 0, a, firstLeg, putIndex); return a; } finally { lock.unlock(); } } public String toString() { return Helpers.collectionToString(this); } /** * Atomically removes all of the elements from this queue. * The queue will be empty after this call returns. */ public void clear() { final ReentrantLock lock = this.lock; lock.lock(); try { int k = count; if (k > 0) { final Object[] items = this.items; final int putIndex = this.putIndex; int i = takeIndex; do { items[i] = null; if (++i == items.length) i = 0; } while (i != putIndex); takeIndex = putIndex; count = 0; if (itrs != null) itrs.queueIsEmpty(); for (; k > 0 && lock.hasWaiters(notFull); k--) notFull.signal(); } } finally { lock.unlock(); } } /** * Returns an iterator over the elements in this queue in proper sequence. * The elements will be returned in order from first (head) to last (tail). * @return an iterator over the elements in this queue in proper sequence */ public Iterator<E> iterator() { return new Itr();//itrs 是 ArrayBlockingQueue 的内部类 Itrs 的对象,它的作用是保证循环数组迭代时的正确性,具体实现比较复杂,这里暂不介绍 } 部分代码略... }

ArrayBlockingQueue的核心操作在此我们只关注队列的创建插入元素获取元素操作。

1、创建ArrayBlockingQueue

通过构造方法即可创建对应的实例。

方法 说明
ArrayBlockingQueue(int capacity, boolean fair) 创建指定容量的数组,创建指定现场访问策略的重入锁和Condition
ArrayBlockingQueue(int capacity) 默认的构造函数只指定了队列的容量并设置为非公平的线程访问策略
**ArrayBlockingQueue(int capacity, boolean fair,Collection c) ** 创建指定容量的数组,创建指定现场访问策略的重入锁和Condition 并插入指定的元素

2、向队列中插入元素

在ArrayBlockingQueue中实现插入元素,有多个方法,本质上还是调用enqueue方法。

方法 说明
add(E e) 如果没有超过队列的容量,直接在队尾处插入指定元素e,add(E) 调用了父类的方法,而父类里没有实现 offer(E),本质上还是调用自身的offer(E),如果返回 false 就抛出异常。
offer(E e) 先申请锁,拿到之后如果立即将e插入队列没有超过最大容量,则调用enqueue将e插入队列尾部,如果队列已满返回false
offer(E,long,TimeUnit) 与offer(E e)大体上功能类似,区别在于可以设置等待超时时间,若已超过还不能有位置则返回 false;否则调用 enqueue(E),然后返回 true。
enqueue(E x) 如果添加元素后队列满了,就修改 putIndex 为 0 ;反之直接添加到数组队列尾部并调用 notEmpty.signal() 通知唤醒阻塞在获取元素的线程
put() 功能和offer类型,put() 方法可以响应中断,当队列满了,就调用 notFull.await() 阻塞等待,等有消费者获取元素后继续执行

3、获取队列中的元素

在ArrayBlockingQueue中实现获取元素,有多个方法,本质上还是调用dequeue方法。

方法 说明
E peek() 直接返回数组中队尾的元素,并不会删除元素。如果队列中没有元素返回的是 null
E poll() 选申请锁,拿到锁之后,如果在队列中没有元素时会立即返回 null;如果有元素调用 dequeue()返回
E poll(long timeout, TimeUnit unit)(E e) 与offer(E e)大体上功能类似,区别在于可以允许阻塞一段时间,如果在阻塞一段时间还没有元素进来,就返回 null
take() 与poll(E e)大体上功能类似,take() 方法可以响应中断,如果队列中没有数据会一直阻塞等待,直到中断或者有元素,有元素时还是调用 dequeue() 方法。
E dequeue() 从队首移除元素(即 takeIndex 位置)移除后会向后移动 takeIndex,如果已经到队尾,就归零,其实 ArrayBlockingQueue 是个环形数组

概括起来ArrayBlockingQueue 使用可重入锁 ReentrantLock 控制队列的插入和获取,两个 Condition 实现生产者 - 消费者模型。可以看出put和take方法主要是通过condition的通知机制来完成可阻塞式的插入数据和获取数据

四、LinkedBlockingQueue

ArrayBlockingQueue 是一个使用数组实现的阻塞队列,而LinkedBlockingQueue则是使用链表实现的有界阻塞队列,当构造对象时为指定队列大小时,队列默认大小为Integer.MAX_VALU,可以通过重载构造方法设定最大值,队列中的元素按 FIFO 的原则进行排序,吞吐量比ArrayBlockingQueue 要大。

package java.util.concurrent;

import java.util.AbstractQueue;
import java.util.Collection;
import java.util.Iterator;
import java.util.NoSuchElementException;
import java.util.Spliterator;
import java.util.Spliterators;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.ReentrantLock;
import java.util.function.Consumer;

/**
 * An optionally-bounded {@linkplain BlockingQueue blocking queue} based on
 * linked nodes.
 * This queue orders elements FIFO (first-in-first-out).
 * The head of the queue is that element that has been on the
 * queue the longest time.
 * The tail of the queue is that element that has been on the
 * queue the shortest time. New elements
 * are inserted at the tail of the queue, and the queue retrieval
 * operations obtain elements at the head of the queue.
 * Linked queues typically have higher throughput than array-based queues but
 * less predictable performance in most concurrent applications.
 *
 * 

The optional capacity bound constructor argument serves as a * way to prevent excessive queue expansion. The capacity, if unspecified, * is equal to {@link Integer#MAX_VALUE}. Linked nodes are * dynamically created upon each insertion unless this would bring the * queue above capacity. * *

This class and its iterator implement all of the * optional methods of the {@link Collection} and {@link * Iterator} interfaces. */ public class LinkedBlockingQueue<E> extends AbstractQueue<E> implements BlockingQueue<E>, java.io.Serializable { /** * Linked list node class. 单向链表的节点类 */ static class Node<E> { E item; //当前节点 /** * 当前节点的后置节点有三种情况:: * - the real successor Node 后置节点存在,代表真正的后置节点 * - this Node, meaning the successor is head.next 取值为当前节点,代表后置节点为head.next * - null, meaning there is no successor (this is the last node) 取值为null,说明该节点就是尾节点 */ Node<E> next; Node(E x) { item = x; } } /** The capacity bound, or Integer.MAX_VALUE if none 队列的容量,默认为 Integer.MAX_VALUE*/ private final int capacity; /** Current number of elements 当前队列中的元素数量*/ private final AtomicInteger count = new AtomicInteger(); /** * Head of linked list. * Invariant: head.item == null 头节点的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, 获取元素的重入锁 */ private final ReentrantLock takeLock = new ReentrantLock(); /** Wait queue for waiting takes */ private final Condition notEmpty = takeLock.newCondition(); /** Lock held by put, offer, 插入元素的重入锁 */ private final ReentrantLock putLock = new ReentrantLock(); /** Wait queue for waiting puts */ private final Condition notFull = putLock.newCondition(); /** * Signals a waiting take. Called only from put/offer (which do not * otherwise ordinarily lock takeLock.) 通过Condition 通知 */ private void signalNotEmpty() { final ReentrantLock takeLock = this.takeLock; takeLock.lock(); try { notEmpty.signal(); } finally { takeLock.unlock(); } } /** * Links node at end of queue. 简单的链表操作,在尾部添加节点node * @param node the node */ private void enqueue(Node<E> node) { last = last.next = node; } /** * Removes a node from head of queue. 从链表头部获取元素 * @return the node */ private E dequeue() { Node<E> h = head; Node<E> first = h.next; h.next = h; // help GC head = first;// E x = first.item; first.item = null; return x; } /** * Creates a {@code LinkedBlockingQueue} with a capacity of * {@link Integer#MAX_VALUE}. */ 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);//初始化头结点 } /** * 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. * * @param c the collection of elements to initially contain * @throws NullPointerException if the specified collection or any * of its elements are null */ 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(); } } /** * Inserts the specified element at the tail of this queue, waiting if * necessary for space to become available. * * @throws InterruptedException {@inheritDoc} * @throws NullPointerException {@inheritDoc} */ 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 所有的put、take 操作都预设一个局部变量用于表示操作是否成功 // holding count negative to indicate failure unless set. int c = -1; Node<E> node = new Node<E>(e);//创建节点 final ReentrantLock putLock = this.putLock;//初始化put重入锁 final AtomicInteger count = this.count; putLock.lockInterruptibly();//申请put 重入锁 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. */ while (count.get() == capacity) { notFull.await();//队列已满则阻塞 } enqueue(node);//否则入队,直接添加到链表尾部 c = count.getAndIncrement(); if (c + 1 < capacity) notFull.signal();//发出未满信号,唤醒其他阻塞的添加线程。 } finally { putLock.unlock(); } if (c == 0) signalNotEmpty(); } /** * Inserts the specified element at the tail of this queue, waiting if * necessary up to the specified wait time for space to become available. * * @return {@code true} if successful, or {@code false} if * the specified waiting time elapses before space is available * @throws InterruptedException {@inheritDoc} * @throws NullPointerException {@inheritDoc} */ 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 <= 0L) 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; } /** * 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(); final AtomicInteger count = this.count; if (count.get() == capacity) return false; int c = -1; Node<E> node = new Node<E>(e); final ReentrantLock putLock = this.putLock; putLock.lock(); try { if (count.get() < capacity) { enqueue(node); c = count.getAndIncrement(); if (c + 1 < capacity) notFull.signal(); } } finally { putLock.unlock(); } if (c == 0) signalNotEmpty(); return c >= 0; } public E take() throws InterruptedException { E x; int c = -1; final AtomicInteger count = this.count; final ReentrantLock takeLock = this.takeLock;//为什么每次都要去拿一遍?TODO takeLock.lockInterruptibly();//申请take 重入锁 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; } public E poll(long timeout, TimeUnit unit) throws InterruptedException { E x = null; int c = -1; long nanos = unit.toNanos(timeout); final AtomicInteger count = this.count; final ReentrantLock takeLock = this.takeLock; takeLock.lockInterruptibly(); try { while (count.get() == 0) { if (nanos <= 0L) return null; nanos = notEmpty.awaitNanos(nanos); } x = dequeue(); c = count.getAndDecrement(); if (c > 1) notEmpty.signal(); } finally { takeLock.unlock(); } if (c == capacity) signalNotFull(); return x; } 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; } public E peek() { if (count.get() == 0) return null; final ReentrantLock takeLock = this.takeLock; takeLock.lock(); try { return (count.get() > 0) ? head.next.item : null; } finally { takeLock.unlock(); } } /** * Unlinks interior Node p with predecessor trail. */ 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.item = null; trail.next = p.next; if (last == p) last = trail; if (count.getAndDecrement() == capacity) notFull.signal(); } /** * 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(); } } 部分代码略... }

说实话我有点不想贴源码,其实大体上逻辑都与ArrayBlockingQueue差不多,同样是在构造方法的时候传入一个指定的容量,区别在于LinkedBlockingQueue 内部是基于单向链表的,虽然都是使用重入锁来控制线程,但是LinkedBlockingQueue 在执行插入和获取操作的时候分别使用不同的两个重入锁,并发插入/获取效率更高些,put 重入锁配合对应的Condition只负责插入操作,而take 重入锁配合另一个Condition只负责获取操作,他们都依赖的“count”计数字段作为原子来维护,以避免在大多数情况下需要获取这两个锁。每当一个元素入队时,putLock被获取并且计数已更新。随后的阅读器保证可见性通过获取putLock(通过fullyLock)或者通过获取takeLock,然后读取n = count.get()

五、PriorityBlockingQueue

PriorityBlockingQueue是内部使用数组支持优先级的无界阻塞队列。默认情况下元素采取自然顺序排列,当然我们可以实现元素的 compareTo() 方法指定元素的排序规则或者在初始化它时在构造函数中传递 Comparator 接口排序规则。如果认真看完上面的源码,相信大家对于阻塞队列模型的设计思想,应该都有所了解了吧,此处就不详细介绍源码细节,仅仅从整体上总结下。

注意:不能保证同优先级元素的顺序

1、创建PriorityBlockingQueue

和其他阻塞队列一样,在构造PriorityBlockingQueue对象时候完成:内部数组的初始化重入锁和对应条件Condition的创建以及比较器的初始化


public class PriorityBlockingQueue extends AbstractQueue implements BlockingQueue, java.io.Serializable {

    /**
     * Default array capacity. 创建指定容量的数组,默认是 11
     */
    private static final int DEFAULT_INITIAL_CAPACITY = 11;

    private static final int MAX_ARRAY_SIZE = Integer.MAX_VALUE - 8;

    /**
     * Priority queue represented as a balanced binary heap: the two
     * children of queue[n] are queue[2*n+1] and queue[2*(n+1)].  The
     * priority queue is ordered by comparator, or by the elements'
     * natural ordering, if comparator is null: For each node n in the
     * heap and each descendant d of n, n <= d.  The element with the
     * lowest value is in queue[0], assuming the queue is nonempty.
	 * 优先级队列表示为平衡二叉树堆:队列[n]的两个子节点是队列[2 * n + 1]和队列[2 *(n + 1)]。该队列由comparator接口决定排序规则,当comparator接口为空按自然顺序排序由元素
     */
    private transient Object[] queue;

    /**
     * The number of elements in the priority queue. 队列的元素数
     */
    private transient int size;

    /**
     * The comparator, or null if priority queue uses elements'natural ordering. 用于定义排序规则
     */
    private transient Comparator comparator;

    /**
     * Lock used for all public operations. 一个控制线程访问的重入锁
     */
    private final ReentrantLock lock;

    /**
     * Condition for blocking when empty. 
     */
    private final Condition notEmpty;

    /**
     * A plain PriorityQueue used only for serialization,
     * to maintain compatibility with previous versions
     * of this class. Non-null only during serialization/deserialization.
	 * PriorityQueue 是一个用数组实现的,基于二进制堆(元素[n] 的子孩子是 元素[2*n+1] 和元素[2*(n+1)] )数据结构的集合。,队列内部排序基于传入的比较器的优先级队列
     */
    private PriorityQueue q;//
	
    public PriorityBlockingQueue() {
        this(DEFAULT_INITIAL_CAPACITY, null);
    }

    public PriorityBlockingQueue(int initialCapacity) {
        this(initialCapacity, null);
    }

    /**
     * Creates a {@code PriorityBlockingQueue} with the specified initial
     * capacity that orders its elements according to the specified
     * comparator.
     *
     * @param initialCapacity the initial capacity for this priority queue
     * @param  comparator the comparator that will be used to order this
     *         priority queue.  If {@code null}, the {@linkplain Comparable
     *         natural ordering} of the elements will be used.
     * @throws IllegalArgumentException if {@code initialCapacity} is less
     *         than 1
     */
    public PriorityBlockingQueue(int initialCapacity,
                                 Comparator comparator) {
        if (initialCapacity < 1)
            throw new IllegalArgumentException();
        this.lock = new ReentrantLock();//创建重入锁
        this.notEmpty = lock.newCondition();//创建条件
        this.comparator = comparator; //初始化比较器
        this.queue = new Object[initialCapacity];//初始化数据,默认长度为11
    }

    /**
     * Creates a {@code PriorityBlockingQueue} containing the elements
     * in the specified collection.  If the specified collection is a
     * {@link SortedSet} or a {@link PriorityQueue}, this
     * priority queue will be ordered according to the same ordering.
     * Otherwise, this priority queue will be ordered according to the
     * {@linkplain Comparable natural ordering} of its elements.
     *
     * @param  c the collection whose elements are to be placed
     *         into this priority queue
     * @throws ClassCastException if elements of the specified collection
     *         cannot be compared to one another according to the priority
     *         queue's ordering
     * @throws NullPointerException if the specified collection or any
     *         of its elements are null
     */
    public PriorityBlockingQueue(Collection c) {
        this.lock = new ReentrantLock();
        this.notEmpty = lock.newCondition();
        boolean heapify = true; // true if not known to be in heap order
        boolean screen = true;  // true if must screen for nulls
        if (c instanceof SortedSet) {
            SortedSet ss = (SortedSet) c;
            this.comparator = (Comparator) ss.comparator();
            heapify = false;
        }
        else if (c instanceof PriorityBlockingQueue) {
            PriorityBlockingQueue pq =
                (PriorityBlockingQueue) c;
            this.comparator = (Comparator) pq.comparator();
            screen = false;
            if (pq.getClass() == PriorityBlockingQueue.class) // exact match
                heapify = false;
        }
        Object[] a = c.toArray();
        int n = a.length;
        // If c.toArray incorrectly doesn't return Object[], copy it.
        if (a.getClass() != Object[].class)
            a = Arrays.copyOf(a, n, Object[].class);
        if (screen && (n == 1 || this.comparator != null)) {
            for (int i = 0; i < n; ++i)
                if (a[i] == null)
                    throw new NullPointerException();
        }
        this.queue = a;
        this.size = n;
        if (heapify)
            heapify();
    }
	。。。部分代码略
}
	

2、PriorityBlockingQueue如何实现无界

PriorityBlockingQueue的插入元素操作,最终都是调用offer方法,于是在每一次offer的时候都会去判断当前数组中的元素是否已经超过原有容量,超过的时候就调用tryGrow方法进行扩容:在扩容时,如果当前队列中元素个数小于 64 个,数组容量就就乘 2 加 2;否则变成原来的 1.5 倍(因为容量越大,扩容成本越高,所以容量够用就行,尽量设置的小一点)。

public boolean add(E e) {
    return offer(e);
}
public void put(E e) {
    offer(e); // never need to block
}
public boolean offer(E e, long timeout, TimeUnit unit) {
    return offer(e); // never need to block
}
public boolean offer(E e) {
    if (e == null)
        throw new NullPointerException();
    final ReentrantLock lock = this.lock;
    lock.lock();
    int n, cap;
    Object[] array;
    while ((n = size) >= (cap = (array = queue).length))    //当前数组中元素大于等于容量时,扩容
        tryGrow(array, cap);
    try {
        Comparator<? super E> cmp = comparator;
        if (cmp == null)
            siftUpComparable(n, e, array);
        else
            siftUpUsingComparator(n, e, array, cmp);
        size = n + 1;
        notEmpty.signal();
    } finally {
        lock.unlock();
    }
    return true;
}
//在扩容时,如果当前队列中元素个数小于 64 个,数组容量就就乘 2 加 2;否则变成原来的 1.5 倍(因为容量越大,扩容成本越高,所以容量够用就行,尽量设置的小一点)
private void tryGrow(Object[] array, int oldCap) {
    lock.unlock(); // must release and then re-acquire main lock
    Object[] newArray = null;
    if (allocationSpinLock == 0 &&
        UNSAFE.compareAndSwapInt(this, allocationSpinLockOffset, 0, 1)) {
        try {
            int newCap = oldCap + ((oldCap < 64) ?
	            (oldCap + 2) : // grow faster if small
                                   (oldCap >> 1));
            if (newCap - MAX_ARRAY_SIZE > 0) {    // possible overflow
                int minCap = oldCap + 1;
                if (minCap < 0 || minCap > MAX_ARRAY_SIZE)
                    throw new OutOfMemoryError();
                newCap = MAX_ARRAY_SIZE;
            }
            if (newCap > oldCap && queue == array)
                newArray = new Object[newCap];    //扩容数组
        } finally {
            allocationSpinLock = 0;
        }
    }
    if (newArray == null) // back off if another thread is allocating
        Thread.yield();
    lock.lock();
    if (newArray != null && queue == array) {
        queue = newArray;
        System.arraycopy(array, 0, newArray, 0, oldCap);    //拷贝原有数据
    }
}

3、实现优先级

首先所谓的优先级表现就是谁先被获取,这直接和插入之后队列内的排列顺序有关,在插入元素的时候会根据传入的比较器是否为空调用不同的堆排序方法,而且在调用dequeue获取的时候也会按照比较器规则重新堆排序,从而保证了优先级。

在二进制堆中,堆是单个树,它是一个完整的二叉树

    /**
     * Mechanics for poll().  Call only while holding lock.
     */
    private E dequeue() {
        int n = size - 1;
        if (n < 0)
            return null;
        else {
            Object[] array = queue;
            E result = (E) array[0];
            E x = (E) array[n];
            array[n] = null;
            Comparator<? super E> cmp = comparator;
            if (cmp == null)
                siftDownComparable(0, x, array, n);
            else
                siftDownUsingComparator(0, x, array, n, cmp);
            size = n;
            return result;
        }
    }

    /**
     * Inserts item x at position k, maintaining heap invariant by
     * promoting x up the tree until it is greater than or equal to
     * its parent, or is the root.
     *
     * To simplify and speed up coercions and comparisons. the
     * Comparable and Comparator versions are separated into different
     * methods that are otherwise identical. (Similarly for siftDown.)
     * These methods are static, with heap state as arguments, to
     * simplify use in light of possible comparator exceptions.
     *	
     * @param k the position to fill
     * @param x the item to insert
     * @param array the heap array
	 * 最大堆的意思?每从队尾添加一个元素都会从下往上挨个比较自己和“父节点”的大小,如果小就交换,否则就停止。
     */
    private static <T> void siftUpComparable(int k, T x, Object[] array) {
        Comparable<? super T> key = (Comparable<? super T>) x;
        while (k > 0) {
            int parent = (k - 1) >>> 1;
            Object e = array[parent];
            if (key.compareTo((T) e) >= 0)
                break;
            array[k] = e;
            k = parent;
        }
        array[k] = key;
    }

    private static <T> void siftUpUsingComparator(int k, T x, Object[] array,
                                       Comparator<? super T> cmp) {
        while (k > 0) {
            int parent = (k - 1) >>> 1;
            Object e = array[parent];
            if (cmp.compare(x, (T) e) >= 0)
                break;
            array[k] = e;
            k = parent;
        }
        array[k] = x;
    }

    /**
     * Inserts item x at position k, maintaining heap invariant by
     * demoting x down the tree repeatedly until it is less than or
     * equal to its children or is a leaf.
     *
     * @param k the position to fill
     * @param x the item to insert
     * @param array the heap array
     * @param n heap size
     */
    private static <T> void siftDownComparable(int k, T x, Object[] array,
                                               int n) {
        if (n > 0) {
            Comparable<? super T> key = (Comparable<? super T>)x;
            int half = n >>> 1;           // loop while a non-leaf
            while (k < half) {
                int child = (k << 1) + 1; // assume left child is least
                Object c = array[child];//左树儿子节点
                int right = child + 1;//右树儿子的位置
                if (right < n &&
                    ((Comparable<? super T>) c).compareTo((T) array[right]) > 0)//左和右相比
                    c = array[child = right];
                if (key.compareTo((T) c) <= 0)
                    break;
                array[k] = c;//交换
                k = child;
            }
            array[k] = key;
        }
    }

    private static <T> void siftDownUsingComparator(int k, T x, Object[] array,
                                                    int n,
                                                    Comparator<? super T> cmp) {
        if (n > 0) {
            int half = n >>> 1;
            while (k < half) {
                int child = (k << 1) + 1;
                Object c = array[child];
                int right = child + 1;
                if (right < n && cmp.compare((T) c, (T) array[right]) > 0)
                    c = array[child = right];
                if (cmp.compare(x, (T) c) <= 0)
                    break;
                array[k] = c;
                k = child;
            }
            array[k] = x;
        }
    }

六、DelayQueue

DelayQueue是内部使用PriorityQueue支持延时获取元素的无界阻塞队列。队列中的元素必须实现Delayed接口,在创建元素时可以指定多久才能从队列中获取当前元素,只有在延迟期满时才能从队列中提取元素。简而言之,DelayQueue本质上就是优先级队列的变种,元素按照超时时间排序,从头到尾,离超时时间越近的元素,越靠前排列,所以对队列中的元素有要求,DelayQueue队列的元素必须实现接口Delayed

1、创建DelayQueue

DelayQueue中四个重要的成员变量都不是在构造方法里进行初始化的,DelayQueue的构造方法里相比于其他阻塞队列,做的事简单多了,如下所示。

public class DelayQueue<E extends Delayed> extends AbstractQueue<E>
    implements BlockingQueue<E> {

    private final transient ReentrantLock lock = new ReentrantLock();//创建重入锁
    private final PriorityQueue<E> q = new PriorityQueue<E>();//创建优先级队列

    /**
     * Thread designated to wait for the element at the head of
     * the queue.  This variant of the Leader-Follower pattern
     * (http://www.cs.wustl.edu/~schmidt/POSA/POSA2/) serves to
     * minimize unnecessary timed waiting.  When a thread becomes
     * the leader, it waits only for the next delay to elapse, but
     * other threads await indefinitely.  The leader thread must
     * signal some other thread before returning from take() or
     * poll(...), unless some other thread becomes leader in the
     * interim.  Whenever the head of the queue is replaced with
     * an element with an earlier expiration time, the leader
     * field is invalidated by being reset to null, and some
     * waiting thread, but not necessarily the current leader, is
     * signalled.  So waiting threads must be prepared to acquire
     * and lose leadership while waiting.
	 * leader是一个等待获取队列头部元素的线程
     */
    private Thread leader;

    /**
     * Condition signalled when a newer element becomes available
     * at the head of the queue or a new thread may need to
     * become leader.
	 * 队首有新元素可用或者有新线程成为 leader 时触发的 condition
     */
    private final Condition available = lock.newCondition();

    public DelayQueue() {}

    /**
     * Creates a {@code DelayQueue} initially containing the elements of the
     * given collection of {@link Delayed} instances.
     *
     * @param c the collection of elements to initially contain
     * @throws NullPointerException if the specified collection or any
     *         of its elements are null
     */
    public DelayQueue(Collection<? extends E> c) {
        this.addAll(c);
    }
    ....部分代码略
  }

2、获取DelayQueue的元素

    /**
     * Retrieves, but does not remove, the head of this queue, or returns {@code null} if this queue is empty.  Unlike
     * {@code poll}, if no expired elements are available in the queue, this method returns the element that will expire next,if one exists.
     *
     * @return the head of this queue, or {@code null} if this queue is empty
	 * 检索并返回此队列的头部元素(但不删除)
     */
    public E peek() {
        final ReentrantLock lock = this.lock;
        lock.lock();
        try {
            return q.peek();
        } finally {
            lock.unlock();
        }
    }
	
	    /**
     * Retrieves and removes the head of this queue, waiting if necessary until an element with an expired delay is available on this queue.
     * @return the head of this queue
     */
    public E take() throws InterruptedException {
        final ReentrantLock lock = this.lock;//1、初始化锁
        lock.lockInterruptibly();//2、申请锁
        try {
			//无限循环
            for (;;) {
                E first = q.peek();//检索并返回此队列的头部元素(但不删除)
                if (first == null)//若PriorityQueue为空,
                    available.await();//阻塞即当前线程到Condition中等待
                else {
                    long delay = first.getDelay(NANOSECONDS);
                    if (delay <= 0L)
                        return q.poll();//Retrieves and removes the head of this queue 获取此队列的头部元素(并删除)
                    first = null; // don't retain ref while waiting
                    if (leader != null)
                        available.await();
                    else {
                        Thread thisThread = Thread.currentThread();
                        leader = thisThread;
                        try {
                            available.awaitNanos(delay);
                        } finally {
                            if (leader == thisThread)
                                leader = null;
                        }
                    }
                }
            }
        } finally {
            if (leader == null && q.peek() != null)
                available.signal();
            lock.unlock();//释放锁
        }
    }

3、插入元素到DelayQueue

    /**
     * Inserts the specified element into this delay queue.
     *
     * @param e the element to add
     * @return {@code true} (as specified by {@link Collection#add})
     * @throws NullPointerException if the specified element is null
     */
    public boolean add(E e) {
        return offer(e);
    }

    /**
     * Inserts the specified element into this delay queue.
     *
     * @param e the element to add
     * @return {@code true}
     * @throws NullPointerException if the specified element is null
     */
    public boolean offer(E e) {
        final ReentrantLock lock = this.lock;
        lock.lock();
        try {
            q.offer(e);
            if (q.peek() == e) {
                leader = null;
                available.signal();
            }
            return true;
        } finally {
            lock.unlock();
        }
    }

4、实现Delayed 接口

DelayQueue队列的元素必须实现Delayed接口。我们可以参考ScheduledThreadPoolExecutor里ScheduledFutureTask类的实现,一共有三步:

  • 在对象创建的时候,初始化基本数据。使用time记录当前对象延迟到什么时候可以使用,使用sequenceNumber来标识元素在队列中的先后顺序
private static final AtomicLong sequencer = new AtomicLong(0);
ScheduledFutureTask(Runnable r, V result, long ns, long period) {
	super(r, result);
	this.time = ns;
	this.period = period;
	this.sequenceNumber = sequencer.getAndIncrement();
}
  • 实现getDelay方法,该方法返回当前元素还需要延时多长时间,单位是纳秒

通过构造函数可以看出延迟时间参数ns的单位是纳秒,自己设计的时候最好使用纳秒,因为实现getDelay()方法时可以指定任意单位,一旦以秒或分作为单位,而延时时间又精确不到纳秒就麻烦了。使用时请注意当time小于当前时间时,getDelay会返回负数。

//0或负值表示延迟已经过去
public long getDelay(TimeUnit unit) {
	return unit.convert(time - now(), TimeUnit.NANOSECONDS);
}
  • 实现compareTo方法来指定元素的顺序
//让延时时间最长的放在队列的末尾。
public int compareTo(Delayed other) {
    if (other == this) // compare zero if same object
        return 0;
    if (other instanceof ScheduledFutureTask) {
        ScheduledFutureTask<?> x = (ScheduledFutureTask<?>)other;
        long diff = time - x.time;
        if (diff < 0)
            return -1;
        else if (diff > 0)
            return 1;
        else if (sequenceNumber < x.sequenceNumber)
            return -1;
        else
            return 1;
    }
    long diff = getDelay(NANOSECONDS) - other.getDelay(NANOSECONDS);
    return (diff < 0) ? -1 : (diff > 0) ? 1 : 0;
}

5、实现延时阻塞队列

延时阻塞队列的实现很简单,当消费者从队列里获取元素时,如果元素没有达到延时时间,就阻塞当前线程。

//在取元素时,会根据元素的延时执行时间是否为 0 进行判断,如果延时执行时间已经没有了,就直接返回;否则就要等待执行时间到达后再返回。
public E take() throws InterruptedException {
    final ReentrantLock lock = this.lock;
    lock.lockInterruptibly();
    try {
        for (;;) {
            E first = q.peek();    //先获取队首元素,不删除
            if (first == null)    //如果为空就阻塞等待
                available.await();
            else {
                long delay = first.getDelay(NANOSECONDS);
                if (delay <= 0L)    //比较元素延时时间是否到达
                    return q.poll();    //如果是就移除并返回
                first = null; // don't retain ref while waiting
                if (leader != null)    //如果有 leader 线程,依然阻塞等待
                    available.await();
                else {        //如果没有 leader 线程,指定当前线程,然后等待任务的待执行时间
                    Thread thisThread = Thread.currentThread();
                    leader = thisThread;
                    try {
                        available.awaitNanos(delay);
                    } finally {
                        if (leader == thisThread)
                            leader = null;
                    }
                }
            }
        }
    } finally {        //最后等待时间到了后,就通知阻塞的线程
        if (leader == null && q.peek() != null)
            available.signal();
        lock.unlock();
    }
}

//PriorityQueue.peek()
public E peek() {
    return (size == 0) ? null : (E) queue[0];
}

代码中的变量leader是一个等待获取队列头部元素的线程。如果leader不等于空,表示已
经有线程在等待获取队列的头元素。所以,使用await()方法让当前线程等待信号。如果leader
等于空,则把当前线程设置成leader,并使用awaitNanos()方法让当前线程等待接收信号或等
待delay时间

6、DelayQueue中的Leader-follower线程模型

IO线程模型一直在演化,由最开始的单线程模型,到BIO方式的单线程接受请求线程池线程具体处理单个请求的读写事件,再到NIO的单线程接受请求线程池里面的单个线程可以处理不同请求的读写事件,一个字没有最快,只有更快。最近发现还有个Leader-follower线程模型,其的出现是为了解决单线程接受请求线程池线程处理请求下线程上下文切换以及线程间通信数据拷贝的开销,并且不需要维护一个队列
Java 进阶——多线程优化之线程池 ThreadPoolExecutor的核心容器阻塞队列详解(一)_第1张图片
在Leader-follower线程模型中每个线程有三种模式:leaderfollowerprocessing,永远有且只有一个 leader角色,而所有 follower 都在等待成为 leader。在Leader-follower线程模型一开始会创建一个线程池,并且会选取一个线程作为leader线程,leader线程负责监听网络请求,其它线程为follower处于waiting状态,当leader线程接受到一个请求后,会释放自己作为leader的权利,然后从follower线程中选择一个线程进行激活,然后激活的线程被选择为新的leader线程作为服务监听,然后老的leader则负责处理自己接受到的请求(现在老的leader线程状态变为了processing),处理完成后,状态从processing转换为follower,这种模式下接受请求和进行处理使用的是同一个线程,这避免了线程上下文切换和线程通讯数据拷贝,增强 了CPU 高速缓存相似性,及消除动态内存分配和线程间的数据交换。这种模式是为了最小化任务等待时间,当一个线程成为 leader 后,它只需要等待下一个可执行任务的出现,而其他线程要无限制地等待。

7、DelayQueue 使用场景:

  • 缓存系统的设计——用 DelayQueue 保存元素的有效期,用一个线程来循环查询 DelayQueue ,能查到元素,就说明缓存的有效期到了。

  • 定时任务调度 ——用 DelayQueue 保存定时执行的任务和执行时间,同样有一个循环查询线程,获取到任务就执行,
    比如TimerQueue 就是使用 DelayQueue 实现的。

七、SynchronousQueue

SynchronousQueue是一个“不存储”元素支持线程公平访问的阻塞队列每一个put操作必须等待一个take操作,否则不能继续添加元素。它支持公平访问队列。默认情况下线程采用非公平性策略访问队列。使用以下构造方法可以创建公平性访问的SynchronousQueue,如果设置为true,则等待的线程会采用先进先出的顺序访问队列。SynchronousQueue可以看成是一个传球手,负责把生产者线程处理的数据直接传递给消费者线程。队列本身并不存储任何元素,非常适合传递性场景。SynchronousQueue的吞吐量高于LinkedBlockingQueue和ArrayBlockingQueue。

“不存储”指的SynchronousQueue 容量为 0,每添加一个元素必须等待被取走后才能继续添加元素。

八、LinkedTransferQueue

LinkedTransferQueue是一个由链表结构组成的无界阻塞TransferQueue队列。相对于其他阻塞队列,LinkedTransferQueue多了tryTransfer和transfer方法。

1、transfer方法

如果当前有消费者正在等待接收元素(消费者使用take()方法或带时间限制的poll()方法时),transfer方法可以把生产者传入的元素立刻transfer(传输)给消费者。如果没有消费者在等待接收元素,transfer方法会将元素存放在队列的tail节点,并等到该元素被消费者消费了才返回。transfer方法的关键代码如下。

Node pred = tryAppend(s, haveData);//试图把存放当前元素的s节点作为tail节点
return awaitMatch(s, pred, e, (how == TIMED), nanos);//让CPU自旋等待消费者消费元素。因为自旋会消耗CPU,所以自旋一定的次数后使用Thread.yield()方法来暂停当前正在执行的线程,并执行其他线程。

2、tryTransfer方法

tryTransfer方法是用来试探生产者传入的元素是否能直接传给消费者。如果没有消费者等待接收元素,则返回false。和transfer方法的区别是tryTransfer方法无论消费者是否接收,方法立即返回,而transfer方法是必须等到消费者消费了才返回。对于带有时间限制的tryTransfer(E e,long timeout,TimeUnit unit)方法,试图把生产者传入的元素直接传给消费者,但是如果没有消费者消费该元素则等待指定的时间再返回,如果超时还没消费元素,则返回false,如果在超时时间内消费了元素,则返回true。

九、LinkedBlockingDeque

LinkedBlockingDeque是一个由链表结构组成的双向阻塞队列。所谓双向队列指的是可以从队列的两端插入和移出元素。双向队列因为多了一个操作队列的入口,在多线程同时入队时,也就减少了一半的竞争。相比其他的阻塞队列,LinkedBlockingDeque多了addFirst、addLast、offerFirst、offerLast、peekFirst和peekLast等方法,以First单词结尾的方法,表示插入、获取(peek)或移除双端队列的第一个元素。以Last单词结尾的方法,表示插入、获取或移除双端队列的最后一个元素。另外,插入方法add等同于addLast,移除方法remove等效于removeFirst。但是take方法却等同于takeFirst不知道是不是JDK的bug,使用时还是用带有First和Last后缀的方法更清楚。在初始化LinkedBlockingDeque时可以设置容量防止其过度膨胀。另外,双向阻塞队列可以运用在“工作窃取”模式中。

小结

阻塞队列作为线程池框架中的核心容器,其重要性是不言而喻的,本质上各种类型的线程池就是因为底层使用了不同的阻塞队列,所以线程池的添加任务和执行任务本质上就是对阻塞队列的操作,篇幅问题,线程池的工作原理和使用请移步下一篇文章。

你可能感兴趣的:(Java,Android多线程开发)