Java ~ Collection/Executor ~ LinkedBlockingDeque【源码】

33 篇文章 0 订阅
17 篇文章 0 订阅

前言


    文中的源码注释/结论是我个人学习过程中的理解/看法,多有漏误,后期的新看法/总结也不会再于本文中修正/添加,因此本文内容只可作为参考/提示使用,最新看法/总结以总结篇为准,链接在本文底部。

一 LinkedBlockingDeque(链接阻塞双端队列)类源码及机制详解


 类

    LinkedBlockingDeque(链接阻塞双端队列)类(下文简称链接阻塞双端队列)是BlockingDeqeue(阻塞双端队列)接口的唯一实现类,采用链表的方式实现。链接阻塞双端队列与LinkedBlockingQueue(链接阻塞队列)类在实现上基本一致,但在其基础上新增了针对队列头部进行插入/放置及针对队列尾部进行移除/拿取、检查等操作的方法及其实现。通俗的讲就是链接阻塞双端队列支持在两端进行插入/放置、移除/拿取、检查等操作,而不同于LinkedBlockingQueue(链接阻塞队列)类必须强制在某端进行。这就使得链接阻塞双端队列相较于LinkedBlockingQueue(链接阻塞队列)类而言有着更高的灵活性,令其可被用于模拟堆栈或其它数据结构。涉及到两端操作的相关方法由BlockingDeqeue(阻塞双端队列)接口或Deqeue(双端队列)接口负责定义,该知识点的详细内容会在下文详述。

    链接阻塞双端队列不允许保存null。不允许保存null实际上是BlockingQueue(阻塞队列)接口的定义,BlockingDeqeue(阻塞双端队列)接口作为其子接口也同样遵循该定义,并延续到了链接阻塞双端队列中。不允许保存null的具体原因是因为null被poll()及peek()等方法作为了表示队列中不存在元素的特殊值。

    链接阻塞双端队列支持有界及无界两种使用方式,即是否人为限制可保存的元素总数。如果在创建链接阻塞双端队列时没有指定具体容量,则其便为无界队列。我们并不推荐使用无界队列,由于元素可无限制保存的原因,一旦元素的移除/拿取速率低于其插入/放置的速率,则很容易导致元素大量堆积造成内存溢出错误。因此在实际开发中,还是推荐指定容量的有界队列用法,并根据业务的实际场景及资源的硬性限制选择合适的容量大小。此外,链接阻塞双端队列在无界队列的实现上并不纯粹,采用的是通过Integer.MAX_VALUE大小的有界队列来模拟无界队列的方式,这在一定程度上违背了无界队列的实现规范,该知识点的详细内容会在下文详述。

    链接阻塞双端队列是线程安全的。线程安全实际上是BlockingQueue(阻塞队列)接口的定义,BlockingDeqeue(阻塞双端队列)接口作为其子接口也同样遵循该定义,并延续到了链接阻塞双端队列中。与LinkedBlockingQueue(链接阻塞队列)类支持更高并发的“双锁”线程安全机制不同,链接阻塞双端队列采用常规的“单锁”线程安全机制,即使用一个ReentrantLock(可重入锁)类对象来保证整体的线程安全,这实际是引入两端操作所带来的负面影响。两端操作的引入打破了阻塞队列插入/放置与移除/拿取方法之间原本头尾隔离的元素布局,使得链接阻塞双端队列中的元素数据成为了一个整体,从而不再适用“双锁”线程安全机制。

    链接阻塞双端队列实现了正/倒序两类迭代器,并且它们都是弱一致性的,即可能迭代到已移除的元素或迭代不到新插入的元素。正/倒序两类迭代器的存在很好的契合了两端操作的执行,而弱一致性则有效兼容了并发的使用环境,使得移除/拿取方法的执行不会导致迭代的异常中断,该知识点的详细内容会在下文详述。

    链接阻塞双端队列虽然与BlockingQueue(阻塞队列)接口一样都被纳入Executor(执行器)框架的范畴,但同时也是Collection(集)框架的成员。

/**
 * An optionally-bounded {@linkplain BlockingDeque blocking deque} based on linked nodes.
 * 一个基于链接节点的可选有界阻塞双端队列。
 * <p>
 * The optional capacity bound constructor argument serves as a way to prevent excessive 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
 * deque above capacity.
 * 可选的容量界限构造器参数作为防止过度膨胀的方法。如果容量没有指定,则等于Integer.MAX_VALUE。链接节点在每次插入后动
 * 态地创建,除非这会导致双端队列超过容量。
 * <p>
 * Most operations run in constant time (ignoring time spent blocking).  Exceptions include {@link #remove(Object) remove},
 * {@link #removeFirstOccurrence removeFirstOccurrence}, {@link #removeLastOccurrence removeLastOccurrence},
 * {@link #contains contains}, {@link #iterator iterator.remove()}, and the bulk operations, all of which run in linear time.
 * 大部分操作在常量时间内运行【即时间复杂度为O(1)】(忽略阻塞花费的时间)。例外包括remove、removeFirstOccurrence、
 * removeFirstOccurrence、removeFirstOccurrence、contains、iterator.remove()以及批量操作,所有这些都在线性时间内运行【即
 * 时间复杂度度O(n)】。
 * <p>
 * This class and its iterator implement all of the <em>optional</em> methods of the {@link Collection} and {@link Iterator} interfaces.
 * 当前类与它的迭代器实现了集与迭代器接口所有的可选操作。
 * <p>
 * This class is a member of the <a href="{@docRoot}/../technotes/guides/collections/index.html"> Java Collections Framework</a>.
 * 当前类是Java集框架的成员。
 *
 * @param <E> the type of elements held in this collection 集持有的元素的类型
 * @author Doug Lea
 * @since 1.6
 */
public class LinkedBlockingDeque<E> extends AbstractQueue<E> implements BlockingDeque<E>, java.io.Serializable {

    /*
     * Implemented as a simple doubly-linked list protected by a single lock and using conditions to manage blocking.
     * 实现是受单锁保护的简单双重链接链表,并使用条件管理阻塞。
     * To implement weakly consistent iterators, it appears we need to keep all Nodes GC-reachable from a predecessor dequeued Node.
     * That would cause two problems:
     * - allow a rogue Iterator to cause unbounded memory retention
     * - cause cross-generational linking of old Nodes to new Nodes if a Node was tenured while live, which generational GCs have a hard
     * time dealing with, causing repeated major collections. However, only non-deleted Nodes need to be reachable from dequeued Nodes,
     * and reachability does not necessarily have to be of the kind understood by the GC.  We use the trick of linking a Node that has just
     * been dequeued to itself.  Such a self-link implicitly means to jump to "first" (for next links) or "last" (for prev links).
     * 为了实现弱一致迭代器,它看起来需要我们保持所有节点GC从前驱已出队的节点可达。这会导致两个问题:
     * - 允许一个非法的迭代器导致无限制的内存保留
     * - 如果一个节点在等待期间进入老年代则会导致旧节点到新节点的跨带引用,这种代GC处理非常困难,会导致重复的Major GC。此外,
     * 只有未删除节点需要从已出队的节点可达,并且可达性不一定是GC所理解的那种【那还能是哪种?】。我们使用在节点刚刚出队时链
     * 接至它自己的技巧。如此暗中地自引用意味着跳跃至头(后遍历)或尾节点(前遍历)。
     */

    /*
     * We have "diamond" multiple interface/abstract class inheritance here, and that introduces ambiguities. Often we want the BlockingDeque
     * javadoc combined with the AbstractQueue implementation, so a lot of method specs are duplicated here.
     * 我们"菱形"的多接口/抽象类继承,这引入了歧义。我们通常希望BlockingDeque javadoc与AbstractQueue实现相结合,因此这里复制了许
     * 多方法规范。
     */

    private static final long serialVersionUID = -387911632671998426L;
    
    ...
}

 字段

    first(头节点) —— 持有头节点的引用。头节点的特性是:当头节点为null时尾节点必然也为null;头节点没有前驱节点并且必然存在项。

/**
 * Pointer to first node. Invariant: (first == null && last == null) || (first.prev == null && first.item != null)
 * 头节点的指针。不变式:(first == null && last == null) || (first.prev == null && first.item != null)
 *
 * @Description: 名称:首个
 * @Description: 作用:持有头节点的引用。头节点的特性是:当头节点为null时尾节点必然也为null;头节点没有前驱节点并且必然存在项。
 * @Description: 逻辑:~
 */
transient Node<E> first;

    last(最后) —— 持有尾节点的引用。尾节点的特性是:当尾节点为null时头节点必然也为null;尾节点没有后继节点并且必然存在项。

/**
 * Pointer to last node. Invariant: (first == null && last == null) || (last.next == null && last.item != null)
 * 尾节点的指针。不变式:(first == null && last == null) || (last.next == null && last.item != null)
 *
 * @Description: 名称:最后
 * @Description: 作用:持有尾节点的引用。尾节点的特性是:当尾节点为null时头节点必然也为null;尾节点没有后继节点并且必然存在项。
 * @Description: 逻辑:~
 */
transient Node<E> last;

    count(总数) —— 记录当前链接阻塞双端队列中元素的总数。

/**
 * Number of items in the deque
 * 在双端队列中项的数量
 *
 * @Description: 名称:总数
 * @Description: 作用:记录当前链接阻塞双端队列中元素的总数。
 * @Description: 逻辑:~
 */
private transient int count;

    capacity(容量) —— 记录当前链接阻塞双端队列的容量。

/**
 * Maximum number of items in the deque
 * 在双端队列中项的最大数量
 *
 * @Description: 名称:容量
 * @Description: 作用:记录当前链接阻塞双端队列的容量。
 * @Description: 逻辑:~
 */
private final int capacity;

    lock(锁) —— 持有可重入锁,保护当前链接阻塞双端队列的线程安全。

/**
 * Main lock guarding all access
 * 主锁保护所有访问
 *
 * @Description: 名称:锁
 * @Description: 作用:持有可重入锁,保护当前链接阻塞双端队列的线程安全。
 * @Description: 逻辑:~
 */
final ReentrantLock lock = new ReentrantLock();

    notEmpty(非空) —— 持有非空条件,用于保存因为没有元素而等待的拿取者。

/**
 * Condition for waiting takes
 * 等待拿取的条件
 *
 * @Description: 名称:非空
 * @Description: 作用:持有非空条件,用于保存因为没有元素而等待的拿取者。
 * @Description: 逻辑:~
 */
private final Condition notEmpty = lock.newCondition();

    notFull(非满) —— 持有非满条件,用于保存因为没有而容量等待的放置者。

/**
 * Condition for waiting puts
 * 等待放置的条件
 *
 * @Description: 名称:非满
 * @Description: 作用:持有非满条件,用于保存因为没有而容量等待的放置者。
 * @Description: 逻辑:~
 */
private final Condition notFull = lock.newCondition();

 构造方法

    public LinkedBlockingDeque() —— 创建一个容量为Integer.MAX_VALUE的链接阻塞双端队列,该链接阻塞双端队列被视为无界队列。

/**
 * Creates a {@code LinkedBlockingDeque} with a capacity of {@link Integer#MAX_VALUE}.
 * 创建一个容量为Integer#MAX_VALUE的链接阻塞双端队列。
 *
 * @Description: 名称:~
 * @Description: 作用:创建一个容量为Integer.MAX_VALUE的链接阻塞双端队列,该链接阻塞双端队列被视为无界队列。
 * @Description: 逻辑:~
 */
public LinkedBlockingDeque() {
    this(Integer.MAX_VALUE);
}

    public LinkedBlockingDeque(int capacity) —— 创建一个指定容量的链接阻塞双端队列。

/**
 * Creates a {@code LinkedBlockingDeque} with the given (fixed) capacity.
 * 创建一个指定(固定)容量的链接阻塞双端队列。
 *
 * @param capacity the capacity of this deque 当前双端队列的容量
 * @throws IllegalArgumentException if {@code capacity} is less than 1
 *                                  非法参数异常:如果容量小于1
 * @Description: 名称:~
 * @Description: 作用:创建一个指定容量的链接阻塞双端队列。
 * @Description: 逻辑:~
 */
public LinkedBlockingDeque(int capacity) {
    if (capacity <= 0) throw new IllegalArgumentException();
    this.capacity = capacity;
}

    public LinkedBlockingDeque(Collection<? extends E> c) —— 创建一个容量为Integer.MAX_VALUE,并按迭代器顺序包含指定集中所有元素的链接阻塞双端队列,该链接阻塞双端队列被视为无界队列。

    方法在按迭代器顺序将指定集中元素加入当前链接阻塞双端队列的过程中会加悲观锁。加锁的目的并不是为了避免竞争,而是确保添加的元素对其它线程可见。

/**
 * Creates a {@code LinkedBlockingDeque} 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.
 * 创建一个容量为Integer#MAX_VALUE,初始包含指定集中所有元素链接阻塞双端队列,元素的新增按指定集迭代器顺序。
 *
 * @param c the collection of elements to initially contain 用于初始包含的元素的集
 * @throws NullPointerException if the specified collection or any of its elements are null
 *                              空指针异常:如果指定集或其任意元素为null
 * @Description: 名称:~
 * @Description: 作用:创建一个容量为Integer.MAX_VALUE,并按迭代器顺序包含指定集中所有元素的链接阻塞双端队列,该链接阻塞
 * @Description: 双端队列被视为无界队列。
 * @Description: 逻辑:方法在按迭代器顺序将指定集中元素加入当前链接阻塞双端队列的过程中会加悲观锁。加锁的目的并不是为了避免
 * @Description: 竞争,而是确保添加的元素对其它线程可见。
 */
public LinkedBlockingDeque(Collection<? extends E> c) {
    this(Integer.MAX_VALUE);
    // Never contended, but necessary for visibility
    // 不竞争,但是为了「元素的」可见性是必要的
    final ReentrantLock lock = this.lock;
    lock.lock();
    try {
        for (E e : c) {
            if (e == null)
                throw new NullPointerException();
            // 将指定集中的元素按迭代器顺序从双端队列的尾部依次加入。
            if (!linkLast(new Node<E>(e)))
                throw new IllegalStateException("Deque full");
        }
    } finally {
        lock.unlock();
    }
}

 方法

    private boolean linkFirst(Node node) —— 链接首个 —— 向当前链接阻塞双端队列的头部插入指定节点并将之设置为头节点后返回true。如果当前链接阻塞双端队列已满溢则返回false。

    方法在悲观锁的保护下先判断是否有剩余容量。如果没有直接返回false;如果有则通过链接的方式将当前头节点设置为指定节点的后继节点,并将指定节点设置为新的头节点。随后再判断当前尾节点是否为null,如果是则将指定节点再设置为尾节点;否则将原头节点的前驱节点设置为指定节点。最后唤醒一个等待中的拿取者(如果存在的话)进行移除/拿取操作。

/**
 * Links node as first element, or returns false if full.
 * 链接节点作为头节点,或者如果满溢则返回false。
 *
 * @Description: 名称:链接首个
 * @Description: 作用:向当前链接阻塞双端队列的头部插入指定节点并将之设置为头节点后返回true。如果当前链接阻塞双端队列已满溢则
 * @Description: 返回false。
 * @Description: 逻辑:方法在悲观锁的保护下先判断是否有剩余容量。如果没有直接返回false;如果有则通过链接的方式将当前头节点设
 * @Description: 置为指定节点的后继节点,并将指定节点设置为新的头节点。随后再判断当前尾节点是否为null,如果是则将指定节点再设
 * @Description: 置为尾节点;否则将原头节点的前驱节点设置为指定节点。最后唤醒一个等待中的拿取者(如果存在的话)进行移除/拿取
 * @Description: 操作。
 */
private boolean linkFirst(Node<E> node) {
    // assert lock.isHeldByCurrentThread();
    if (count >= capacity)
        return false;
    Node<E> f = first;
    node.next = f;
    first = node;
    if (last == null)
        last = node;
    else
        f.prev = node;
    ++count;
    notEmpty.signal();
    return true;
}

    private boolean linkLast(Node node) —— 链接最后 —— 向当前链接阻塞双端队列的尾部插入指定节点并将之设置为尾节点后返回true。如果当前链接阻塞双端队列已满溢则返回false。

    方法在悲观锁的保护下先判断是否有剩余容量。如果没有直接返回false;如果有则通过链接的方式将当前尾节点设置为指定节点的前驱节点,并将指定节点设置为新的尾节点。随后再判断当前头节点是否为null,如果是则将指定节点再设置为头节点;否则将原尾节点的后继节点设置为指定节点。最后唤醒一个等待中的拿取者(如果存在的话)进行移除/拿取操作。

/**
 * Links node as last element, or returns false if full.
 * 链接节点作为尾节点,或者如果满溢则返回false。
 *
 * @Description: 名称:链接最后
 * @Description: 作用:向当前链接阻塞双端队列的尾部插入指定节点并将之设置为尾节点后返回true。如果当前链接阻塞双端队列已满溢则
 * @Description: 返回false。
 * @Description: 逻辑:方法在悲观锁的保护下先判断是否有剩余容量。如果没有直接返回false;如果有则通过链接的方式将当前尾节点设
 * @Description: 置为指定节点的前驱节点,并将指定节点设置为新的尾节点。随后再判断当前头节点是否为null,如果是则将指定节点再设
 * @Description: 置为头节点;否则将原尾节点的后继节点设置为指定节点。最后唤醒一个等待中的拿取者(如果存在的话)进行移除/拿取
 * @Description: 操作。
 */
private boolean linkLast(Node<E> node) {
    // assert lock.isHeldByCurrentThread();
    if (count >= capacity)
        return false;
    Node<E> l = last;
    node.prev = l;
    last = node;
    if (first == null)
        first = node;
    else
        l.next = node;
    ++count;
    notEmpty.signal();
    return true;
}

    private E unlinkFirst() —— 断开链接首个 —— 从当前链接阻塞双端队列的头部移除节点并返回元素。如果当前链接阻塞双端队列为空则返回null。

    方法在悲观锁的保护下先判断是否为空。如果是则直接返回null;否则通过断开链接的方式将当前头节点从当前链接阻塞双端队列中移除,断开元素引用并将其后继引用设置为自引用以利于GC,避免跨带引用。随后将后继节点设置为头节点。接着再判断后继节点是否为null,是则说明当前链接阻塞双端队列只有一个节点,移除后会使当前链接阻塞双端队列为空,因此将当前尾节点也设置为空;否则将后继节点的前驱引用设置为null。最后唤醒一个等待中的放置者(如果存在的话)进行插入/放置操作,返回移除的头节点的元素。

/**
 * Removes and returns first element, or null if empty.
 * 移除并返回头元素,或者如果为空则返回null。
 *
 * @Description: 名称:断开链接首个
 * @Description: 作用:从当前链接阻塞双端队列的头部移除节点并返回元素。如果当前链接阻塞双端队列为空则返回null。
 * @Description: 逻辑:方法在悲观锁的保护下先判断是否为空。如果是则直接返回null;否则通过断开链接的方式将当前头节点从当前链接
 * @Description: 阻塞双端队列中移除,断开元素引用并将其后继引用设置为自引用以利于GC,避免跨带引用。随后将后继节点设置为头节
 * @Description: 点。接着再判断后继节点是否为null,是则说明当前链接阻塞双端队列只有一个节点,移除后会使当前链接阻塞双端队列为
 * @Description: 空,因此将当前尾节点也设置为空;否则将后继节点的前驱引用设置为null。最后唤醒一个等待中的放置者(如果存在的话)
 * @Description: 进行插入/放置操作,返回移除的头节点的元素。
 */
private E unlinkFirst() {
    // assert lock.isHeldByCurrentThread();
    Node<E> f = first;
    if (f == null)
        return null;
    Node<E> n = f.next;
    E item = f.item;
    f.item = null;
    f.next = f; // help GC
    first = n;
    if (n == null)
        last = null;
    else
        n.prev = null;
    --count;
    notFull.signal();
    return item;
}

    private E unlinkLast() —— 断开链接最后 —— 从当前链接阻塞双端队列的头部移除节点并返回元素。如果当前链接阻塞双端队列为空则返回null。

    方法在悲观锁的保护下先判断是否为空。如果是则直接返回null;否则通过断开链接的方式将当前尾节点从当前链接阻塞双端队列中移除,断开元素引用并将其前驱引用设置为自引用以利于GC,避免跨带引用。随后将前驱节点设置为尾节点。接着再判断前驱节点是否为null,是则说明当前链接阻塞双端队列只有一个节点,移除后会使当前链接阻塞双端队列为空,因此将当前头节点也设置为空;否则将前驱节点的后继引用设置为null。最后唤醒一个等待中的放置者(如果存在的话)进行插入/放置操作,返回移除的尾节点的元素。

/**
 * Removes and returns last element, or null if empty.
 * 移除并返回头元素,或者如果为空则返回null。
 *
 * @Description: 名称:断开链接最后
 * @Description: 作用:从当前链接阻塞双端队列的头部移除节点并返回元素。如果当前链接阻塞双端队列为空则返回null。
 * @Description: 逻辑:方法在悲观锁的保护下先判断是否为空。如果是则直接返回null;否则通过断开链接的方式将当前尾节点从当前链接
 * @Description: 阻塞双端队列中移除,断开元素引用并将其前驱引用设置为自引用以利于GC,避免跨带引用。随后将前驱节点设置为尾节
 * @Description: 点。接着再判断前驱节点是否为null,是则说明当前链接阻塞双端队列只有一个节点,移除后会使当前链接阻塞双端队列为
 * @Description: 空,因此将当前头节点也设置为空;否则将前驱节点的后继引用设置为null。最后唤醒一个等待中的放置者(如果存在的话)
 * @Description: 进行插入/放置操作,返回移除的尾节点的元素。
 */
private E unlinkLast() {
    // assert lock.isHeldByCurrentThread();
    Node<E> l = last;
    if (l == null)
        return null;
    Node<E> p = l.prev;
    E item = l.item;
    l.item = null;
    l.prev = l; // help GC
    last = p;
    if (p == null)
        first = null;
    else
        p.next = null;
    --count;
    notFull.signal();
    return item;
}

    void unlink(Node x) —— 断开链接 —— 从当前链接阻塞双端队列中断开指定节点的链接,可能是头部/尾部移除,也可能是内部移除。

    方法在悲观锁的保护下先判断是否是头/尾节点,是则执行头/尾节点移除操作;否则通过锻炼链接的方式将指定节点的前驱节点与后继节点相链接,已达到将指定节点内部移除的目的。但指定节点前驱/后继引用并不会被设置为自引用,因为指定节点可能保存在迭代器中,保持原引用有助于迭代器维持迭代,使得迭代器的弱一致性得以实现。

/**
 * Unlinks x.
 * 断开x的链接。
 *
 * @Description: 名称:断开链接
 * @Description: 作用:从当前链接阻塞双端队列中断开指定节点的链接,可能是头部/尾部移除,也可能是内部移除。
 * @Description: 逻辑:方法在悲观锁的保护下先判断是否是头/尾节点,是则执行头/尾节点移除操作;否则通过锻炼链接的方式将指定节
 * @Description: 点的前驱节点与后继节点相链接,已达到将指定节点内部移除的目的。但指定节点前驱/后继引用并不会被设置为自引用,
 * @Description: 因为指定节点可能保存在迭代器中,保持原引用有助于迭代器维持迭代,使得迭代器的弱一致性得以实现。
 */
void unlink(Node<E> x) {
    // assert lock.isHeldByCurrentThread();
    Node<E> p = x.prev;
    Node<E> n = x.next;
    if (p == null) {
        unlinkFirst();
    } else if (n == null) {
        unlinkLast();
    } else {
        p.next = n;
        n.prev = p;
        x.item = null;
        // Don't mess with x's links.  They may still be in use by an iterator.
        // 不要弄乱x的链接。它们可能依然在迭代器中使用。
        --count;
        notFull.signal();
    }
}

    public void addFirst(E e) —— 新增首个 —— 向当前链接阻塞双端队列的头部插入指定元素。该方法是头部插入/放置方法“异常”形式的实现,当链接阻塞双端队列存在剩余容量时插入/放置成功;否则抛出非法状态异常。

    方法直接调用offerFirst(E e)方法实现。

/**
 * @throws IllegalStateException if this deque is full
 *                               非法状态异常:如果当前双端队列已溢满
 * @throws NullPointerException  {@inheritDoc} 空指针异常
 * @Description: 名称:新增首个
 * @Description: 作用:向当前链接阻塞双端队列的头部插入指定元素。该方法是头部插入/放置方法“异常”形式的实现,当链接阻塞双端队
 * @Description: 列存在剩余容量时插入/放置成功;否则抛出非法状态异常。
 * @Description: 逻辑:方法直接调用offerFirst(E e)方法实现。
 */
public void addFirst(E e) {
    if (!offerFirst(e))
        throw new IllegalStateException("Deque full");
}

    public void addLast(E e) —— 新增最后 —— 向当前链接阻塞双端队列的尾部插入指定元素。该方法是尾部插入/放置方法“异常”形式的实现,当链接阻塞双端队列存在剩余容量时插入/放置成功;否则抛出非法状态异常。

    方法直接调用offerLast(E e)方法实现。

/**
 * @throws IllegalStateException if this deque is full
 *                               非法状态异常:如果当前双端队列已溢满
 * @throws NullPointerException  {@inheritDoc} 空指针异常
 * @Description: 名称:新增最后
 * @Description: 作用:向当前链接阻塞双端队列的尾部插入指定元素。该方法是尾部插入/放置方法“异常”形式的实现,当链接阻塞双端队
 * @Description: 列存在剩余容量时插入/放置成功;否则抛出非法状态异常。
 * @Description: 逻辑:方法直接调用offerLast(E e)方法实现。
 */
public void addLast(E e) {
    if (!offerLast(e))
        throw new IllegalStateException("Deque full");
}

    public boolean offerFirst(E e) —— 提供首个 —— 向当前链接阻塞双端队列的头部插入指定元素。该方法是头部插入/放置方法“特殊值”形式的实现,当链接阻塞双端队列存在剩余容量时插入/放置成功并返回true;否则返回false。

    方法在悲观锁的保护下调用linkFirst(Node node)方法实现。

/**
 * @throws NullPointerException {@inheritDoc} 空指针异常
 * @Description: 名称:提供首个
 * @Description: 作用:向当前链接阻塞双端队列的头部插入指定元素。该方法是头部插入/放置方法“特殊值”形式的实现,当链接阻塞双端
 * @Description: 队列存在剩余容量时插入/放置成功并返回true;否则返回false。
 * @Description: 逻辑:方法在悲观锁的保护下调用linkFirst(Node<E> node)方法实现。
 */
public boolean offerFirst(E e) {
    if (e == null) throw new NullPointerException();
    Node<E> node = new Node<E>(e);
    final ReentrantLock lock = this.lock;
    lock.lock();
    try {
        return linkFirst(node);
    } finally {
        lock.unlock();
    }
}

    public boolean offerLast(E e) —— 提供最后 —— 向当前链接阻塞双端队列的尾部插入指定元素。该方法是尾部插入/放置方法“特殊值”形式的实现,当链接阻塞双端队列存在剩余容量时插入/放置成功并返回true;否则返回false。

    方法在悲观锁的保护下调用linkLast(Node node)方法实现。

/**
 * @throws NullPointerException {@inheritDoc} 空指针异常
 * @Description: 名称:提供最后
 * @Description: 作用:向当前链接阻塞双端队列的尾部插入指定元素。该方法是尾部插入/放置方法“特殊值”形式的实现,当链接阻塞双端
 * @Description: 队列存在剩余容量时插入/放置成功并返回true;否则返回false。
 * @Description: 逻辑:方法在悲观锁的保护下调用linkLast(Node<E> node)方法实现。
 */
public boolean offerLast(E e) {
    if (e == null) throw new NullPointerException();
    Node<E> node = new Node<E>(e);
    final ReentrantLock lock = this.lock;
    lock.lock();
    try {
        return linkLast(node);
    } finally {
        lock.unlock();
    }
}

    public void putFirst(E e) throws InterruptedException —— 放置首个 —— 向当前链接阻塞双端队列的头部插入指定元素。该方法是头部插入/放置方法“阻塞”形式的实现,当链接阻塞双端队列存在剩余容量时插入/放置成功;否则等待至存在剩余容量为止。

    方法在悲观锁的保护下循环的调用linkFirst(Node node)方法,如果方法执行失败返回false意味着当前链接阻塞双端队列中没有剩余空间,令当前放置者无限等待。直至被唤醒。唤醒后的当前放置者由于竞争的原因也未必能成功插入,因此需要不断循环的执行该操作,直至成功为止。

/**
 * @throws NullPointerException {@inheritDoc} 空指针异常
 * @throws InterruptedException {@inheritDoc} 中断异常
 * @Description: 名称:放置首个
 * @Description: 作用:向当前链接阻塞双端队列的头部插入指定元素。该方法是头部插入/放置方法“阻塞”形式的实现,当链接阻塞双端队
 * @Description: 列存在剩余容量时插入/放置成功;否则等待至存在剩余容量为止。
 * @Description: 逻辑:方法在悲观锁的保护下循环的调用linkFirst(Node<E> node)方法,如果方法执行失败返回false意味着当前链接阻塞
 * @Description: 双端队列中没有剩余空间,令当前放置者无限等待。直至被唤醒。唤醒后的当前放置者由于竞争的原因也未必能成功插入,
 * @Description: 因此需要不断循环的执行该操作,直至成功为止。
 */
public void putFirst(E e) throws InterruptedException {
    if (e == null) throw new NullPointerException();
    Node<E> node = new Node<E>(e);
    final ReentrantLock lock = this.lock;
    lock.lock();
    try {
        while (!linkFirst(node))
            notFull.await();
    } finally {
        lock.unlock();
    }
}

    public void putLast(E e) throws InterruptedException —— 放置最后 —— 向当前链接阻塞双端队列的尾部插入指定元素。该方法是尾部插入/放置方法“阻塞”形式的实现,当链接阻塞双端队列存在剩余容量时插入/放置成功;否则等待至存在剩余容量为止。

    方法在悲观锁的保护下循环的调用linkLast(Node node)方法,如果方法执行失败返回false意味着当前链接阻塞双端队列中没有剩余空间,令当前放置者无限等待。直至被唤醒。唤醒后的当前放置者由于竞争的原因也未必能成功插入,因此需要不断循环的执行该操作,直至成功为止。

/**
 * @throws NullPointerException {@inheritDoc} 空指针异常
 * @throws InterruptedException {@inheritDoc} 中断异常
 * @Description: 名称:放置最后
 * @Description: 作用:向当前链接阻塞双端队列的尾部插入指定元素。该方法是尾部插入/放置方法“阻塞”形式的实现,当链接阻塞双端队
 * @Description: 列存在剩余容量时插入/放置成功;否则等待至存在剩余容量为止。
 * @Description: 逻辑:方法在悲观锁的保护下循环的调用linkLast(Node<E> node)方法,如果方法执行失败返回false意味着当前链接阻塞
 * @Description: 双端队列中没有剩余空间,令当前放置者无限等待。直至被唤醒。唤醒后的当前放置者由于竞争的原因也未必能成功插入,
 * @Description: 因此需要不断循环的执行该操作,直至成功为止。
 */
public void putLast(E e) throws InterruptedException {
    if (e == null) throw new NullPointerException();
    Node<E> node = new Node<E>(e);
    final ReentrantLock lock = this.lock;
    lock.lock();
    try {
        while (!linkLast(node))
            notFull.await();
    } finally {
        lock.unlock();
    }
}

    public boolean offerFirst(E e, long timeout, TimeUnit unit) throws InterruptedException —— 提供首个 —— 向当前链接阻塞双端队列的头部插入指定元素。该方法是头部插入/放置方法“超时”形式的实现,当链接阻塞双端队列存在剩余容量时插入/放置成功并返回true;否则在指定等待时间内等待至存在剩余容量,超出等待时间则返回false。

    方法在悲观锁的保护下循环的调用linkFirst(Node node)方法,如果方法执行失败返回false意味着当前链接阻塞双端队列中没有剩余空间,令当前放置者有限等待。直至被唤醒或因为超时而唤醒。唤醒后的当前放置者由于竞争的原因也未必能成功插入,因此需要不断循环的执行该操作,直至成功或超时为止。

/**
 * @throws NullPointerException {@inheritDoc} 空指针异常
 * @throws InterruptedException {@inheritDoc} 中断异常
 * @Description: 名称:提供首个
 * @Description: 作用:向当前链接阻塞双端队列的头部插入指定元素。该方法是头部插入/放置方法“超时”形式的实现,当链接阻塞双端队
 * @Description: 列存在剩余容量时插入/放置成功并返回true;否则在指定等待时间内等待至存在剩余容量,超出等待时间则返回false。
 * @Description: 逻辑:方法在悲观锁的保护下循环的调用linkFirst(Node<E> node)方法,如果方法执行失败返回false意味着当前链接阻塞
 * @Description: 双端队列中没有剩余空间,令当前放置者有限等待。直至被唤醒或因为超时而唤醒。唤醒后的当前放置者由于竞争的原因
 * @Description: 也未必能成功插入,因此需要不断循环的执行该操作,直至成功或超时为止。
 */
public boolean offerFirst(E e, long timeout, TimeUnit unit) throws InterruptedException {
    if (e == null) throw new NullPointerException();
    Node<E> node = new Node<E>(e);
    long nanos = unit.toNanos(timeout);
    final ReentrantLock lock = this.lock;
    lock.lockInterruptibly();
    try {
        while (!linkFirst(node)) {
            if (nanos <= 0)
                return false;
            nanos = notFull.awaitNanos(nanos);
        }
        return true;
    } finally {
        lock.unlock();
    }
}

    public boolean offerLast(E e, long timeout, TimeUnit unit) throws InterruptedException —— 提供最后 —— 向当前链接阻塞双端队列的尾部插入指定元素。该方法是尾部插入/放置方法“超时”形式的实现,当链接阻塞双端队列存在剩余容量时插入/放置成功并返回true;否则在指定等待时间内等待至存在剩余容量,超出等待时间则返回false。

    方法在悲观锁的保护下循环的调用linkLast(Node node)方法,如果方法执行失败返回false意味着当前链接阻塞双端队列中没有剩余空间,令当前放置者有限等待。直至被唤醒或因为超时而唤醒。唤醒后的当前放置者由于竞争的原因也未必能成功插入,因此需要不断循环的执行该操作,直至成功或超时为止。

/**
 * @throws NullPointerException {@inheritDoc} 空指针异常
 * @throws InterruptedException {@inheritDoc} 中断异常
 * @Description: 名称:提供最后
 * @Description: 作用:向当前链接阻塞双端队列的尾部插入指定元素。该方法是尾部插入/放置方法“超时”形式的实现,当链接阻塞双端队
 * @Description: 列存在剩余容量时插入/放置成功并返回true;否则在指定等待时间内等待至存在剩余容量,超出等待时间则返回false。
 * @Description: 逻辑:方法在悲观锁的保护下循环的调用linkLast(Node<E> node)方法,如果方法执行失败返回false意味着当前链接阻塞
 * @Description: 双端队列中没有剩余空间,令当前放置者有限等待。直至被唤醒或因为超时而唤醒。唤醒后的当前放置者由于竞争的原因
 * @Description: 也未必能成功插入,因此需要不断循环的执行该操作,直至成功或超时为止。
 */
public boolean offerLast(E e, long timeout, TimeUnit unit) throws InterruptedException {
    if (e == null) throw new NullPointerException();
    Node<E> node = new Node<E>(e);
    long nanos = unit.toNanos(timeout);
    final ReentrantLock lock = this.lock;
    lock.lockInterruptibly();
    try {
        while (!linkLast(node)) {
            if (nanos <= 0)
                return false;
            nanos = notFull.awaitNanos(nanos);
        }
        return true;
    } finally {
        lock.unlock();
    }
}

    public E removeFirst() —— 移除首个 —— 从当前链接阻塞双端队列的头部移除并获取元素。该方法是头部移除/拿取方法中“异常”形式的实现,当链接阻塞双端队列存在元素时移除/拿取并返回头元素;否则抛出无如此元素异常。

    方法直接调用pollFirst()方法实现。

/**
 * @throws NoSuchElementException {@inheritDoc} 无如此元素异常
 * @Description: 名称:移除首个
 * @Description: 作用:从当前链接阻塞双端队列的头部移除并获取元素。该方法是头部移除/拿取方法中“异常”形式的实现,当链接阻塞双
 * @Description: 端队列存在元素时移除/拿取并返回头元素;否则抛出无如此元素异常。
 * @Description: 逻辑:方法直接调用pollFirst()方法实现。
 */
public E removeFirst() {
    E x = pollFirst();
    if (x == null) throw new NoSuchElementException();
    return x;
}

    public E removeLast() —— 移除最后 —— 从当前链接阻塞双端队列的尾部移除并获取元素。该方法是尾部移除/拿取方法中“异常”形式的实现,当链接阻塞双端队列存在元素时移除/拿取并返回尾元素;否则抛出无如此元素异常。

    方法直接调用pollLast()方法实现。

/**
 * @throws NoSuchElementException {@inheritDoc} 无如此元素异常
 * @Description: 名称:移除最后
 * @Description: 作用:从当前链接阻塞双端队列的尾部移除并获取元素。该方法是尾部移除/拿取方法中“异常”形式的实现,当链接阻塞双
 * @Description: 端队列存在元素时移除/拿取并返回尾元素;否则抛出无如此元素异常。
 * @Description: 逻辑:方法直接调用pollLast()方法实现。
 */
public E removeLast() {
    E x = pollLast();
    if (x == null) throw new NoSuchElementException();
    return x;
}

    public E pollFirst() —— 轮询首个 —— 从当前链接阻塞双端队列的头部移除并获取元素。该方法是头部移除/拿取方法中“特殊值”形式的实现,当链接阻塞双端队列存在元素时移除/拿取并返回头元素;否则返回null。

    方法在悲观锁的保护下直接调用unlinkFirst()方法实现。

/**
 * @Description: 名称:轮询首个
 * @Description: 作用:从当前链接阻塞双端队列的头部移除并获取元素。该方法是头部移除/拿取方法中“特殊值”形式的实现,当链接阻塞
 * @Description: 双端队列存在元素时移除/拿取并返回头元素;否则返回null。
 * @Description: 逻辑:方法在悲观锁的保护下直接调用unlinkFirst()方法实现。
 */
public E pollFirst() {
    final ReentrantLock lock = this.lock;
    lock.lock();
    try {
        return unlinkFirst();
    } finally {
        lock.unlock();
    }
}

    public E pollLast() —— 轮询最后 —— 从当前链接阻塞双端队列的尾部移除并获取元素。该方法是尾部移除/拿取方法中“特殊值”形式的实现,当链接阻塞双端队列存在元素时移除/拿取并返回尾元素;否则返回null。

    方法在悲观锁的保护下直接调用unlinkLast()方法实现。

/**
 * @Description: 名称:轮询最后
 * @Description: 作用:从当前链接阻塞双端队列的尾部移除并获取元素。该方法是尾部移除/拿取方法中“特殊值”形式的实现,当链接阻塞
 * @Description: 双端队列存在元素时移除/拿取并返回尾元素;否则返回null。
 * @Description: 逻辑:方法在悲观锁的保护下直接调用unlinkLast()方法实现。
 */
public E pollLast() {
    final ReentrantLock lock = this.lock;
    lock.lock();
    try {
        return unlinkLast();
    } finally {
        lock.unlock();
    }
}

    public E takeFirst() throws InterruptedException —— 拿取首个 —— 从当前链接阻塞双端队列的头部移除并获取元素。该方法是头部移除/拿取方法中“阻塞”形式的实现,当链接阻塞双端队列存在元素时移除/拿取并返回头元素;否则等待至存在元素。

    方法在悲观锁的保护下调用unlinkFirst()方法实现。如果失败,说明当前链接阻塞双端队列不存在元素,令当前拿取者无限等待,直至因为存在元素而被放置者唤醒。唤醒后的当前拿取者由于其它拿取者竞争的原因可能再次失败,因此需要循环的重复上述流程,直至移除成功并返回头元素为止。

/**
 * @Description: 名称:拿取首个
 * @Description: 作用:从当前链接阻塞双端队列的头部移除并获取元素。该方法是头部移除/拿取方法中“阻塞”形式的实现,当链接阻塞双
 * @Description: 端队列存在元素时移除/拿取并返回头元素;否则等待至存在元素。
 * @Description: 逻辑:方法在悲观锁的保护下调用unlinkFirst()方法实现。如果失败,说明当前链接阻塞双端队列不存在元素,令当前拿取
 * @Description: 者无限等待,直至因为存在元素而被放置者唤醒。唤醒后的当前拿取者由于其它拿取者竞争的原因可能再次失败,因此需要
 * @Description: 循环的重复上述流程,直至移除成功并返回头元素为止。
 */
public E takeFirst() throws InterruptedException {
    final ReentrantLock lock = this.lock;
    lock.lock();
    try {
        E x;
        while ((x = unlinkFirst()) == null)
            notEmpty.await();
        return x;
    } finally {
        lock.unlock();
    }
}

    public E takeLast() throws InterruptedException —— 拿取最后 —— 从当前链接阻塞双端队列的尾部移除并获取元素。该方法是尾部移除/拿取方法中“阻塞”形式的实现,当链接阻塞双端队列存在元素时移除/拿取并返回尾元素;否则等待至存在元素。

    方法在悲观锁的保护下调用unlinkLast()方法实现。如果失败,说明当前链接阻塞双端队列不存在元素,令当前拿取者无限等待,直至因为存在元素而被放置者唤醒。唤醒后的当前拿取者由于其它拿取者竞争的原因可能再次失败,因此需要循环的重复上述流程,直至移除成功并返回尾元素为止。

/**
 * @Description: 名称:拿取最后
 * @Description: 作用:从当前链接阻塞双端队列的尾部移除并获取元素。该方法是尾部移除/拿取方法中“阻塞”形式的实现,当链接阻塞双
 * @Description: 端队列存在元素时移除/拿取并返回尾元素;否则等待至存在元素。
 * @Description: 逻辑:方法在悲观锁的保护下调用unlinkLast()方法实现。如果失败,说明当前链接阻塞双端队列不存在元素,令当前拿取
 * @Description: 者无限等待,直至因为存在元素而被放置者唤醒。唤醒后的当前拿取者由于其它拿取者竞争的原因可能再次失败,因此需要
 * @Description: 循环的重复上述流程,直至移除成功并返回尾元素为止。
 */
public E takeLast() throws InterruptedException {
    final ReentrantLock lock = this.lock;
    lock.lock();
    try {
        E x;
        while ((x = unlinkLast()) == null)
            notEmpty.await();
        return x;
    } finally {
        lock.unlock();
    }
}

    public E pollFirst(long timeout, TimeUnit unit) throws InterruptedException —— 轮询首个 —— 从当前链接阻塞双端队列的头部移除并获取元素。该方法是头部移除/拿取方法中“超时”形式的实现,当链接阻塞双端队列存在元素时移除/拿取并返回头元素;否则在指定等待时间内等待至存在元素,超出指定等待时间则返回null。

    方法在悲观锁的保护下调用unlinkFirst()方法实现。如果失败,说明当前链接阻塞双端队列不存在元素,令当前拿取者有限等待,直至因为存在元素而被放置者唤醒或因为超时而唤醒。唤醒后的当前拿取者由于其它拿取者竞争的原因可能再次失败,因此需要循环的重复上述流程,直至移除成功并返回头元素或超时而返回null为止。

/**
 * @Description: 名称:轮询首个
 * @Description: 作用:从当前链接阻塞双端队列的头部移除并获取元素。该方法是头部移除/拿取方法中“超时”形式的实现,当链接阻塞双端
 * @Description: 队列存在元素时移除/拿取并返回头元素;否则在指定等待时间内等待至存在元素,超出指定等待时间则返回null。
 * @Description: 逻辑:方法在悲观锁的保护下调用unlinkFirst()方法实现。如果失败,说明当前链接阻塞双端队列不存在元素,令当前拿取者
 * @Description: 有限等待,直至因为存在元素而被放置者唤醒或因为超时而唤醒。唤醒后的当前拿取者由于其它拿取者竞争的原因可能再次
 * @Description: 失败,因此需要循环的重复上述流程,直至移除成功并返回头元素或超时而返回null为止。
 */
public E pollFirst(long timeout, TimeUnit unit) throws InterruptedException {
    long nanos = unit.toNanos(timeout);
    final ReentrantLock lock = this.lock;
    lock.lockInterruptibly();
    try {
        E x;
        while ((x = unlinkFirst()) == null) {
            if (nanos <= 0)
                return null;
            nanos = notEmpty.awaitNanos(nanos);
        }
        return x;
    } finally {
        lock.unlock();
    }
}

    public E pollLast(long timeout, TimeUnit unit) throws InterruptedException —— 轮询最后 —— 从当前链接阻塞双端队列的尾部移除并获取元素。该方法是尾部移除/拿取方法中“超时”形式的实现,当链接阻塞双端队列存在元素时移除/拿取并返回尾元素;否则在指定等待时间内等待至存在元素,超出指定等待时间则返回null。

    方法在悲观锁的保护下调用unlinkLast()方法实现。如果失败,说明当前链接阻塞双端队列不存在元素,令当前拿取者有限等待,直至因为存在元素而被放置者唤醒或因为超时而唤醒。唤醒后的当前拿取者由于其它拿取者竞争的原因可能再次失败,因此需要循环的重复上述流程,直至移除成功并返回尾元素或超时而返回null为止。

/**
 * @Description: 名称:轮询最后
 * @Description: 作用:从当前链接阻塞双端队列的尾部移除并获取元素。该方法是尾部移除/拿取方法中“超时”形式的实现,当链接阻塞双端
 * @Description: 队列存在元素时移除/拿取并返回尾元素;否则在指定等待时间内等待至存在元素,超出指定等待时间则返回null。
 * @Description: 逻辑:方法在悲观锁的保护下调用unlinkLast()方法实现。如果失败,说明当前链接阻塞双端队列不存在元素,令当前拿取者
 * @Description: 有限等待,直至因为存在元素而被放置者唤醒或因为超时而唤醒。唤醒后的当前拿取者由于其它拿取者竞争的原因可能再次
 * @Description: 失败,因此需要循环的重复上述流程,直至移除成功并返回尾元素或超时而返回null为止。
 */
public E pollLast(long timeout, TimeUnit unit) throws InterruptedException {
    long nanos = unit.toNanos(timeout);
    final ReentrantLock lock = this.lock;
    lock.lockInterruptibly();
    try {
        E x;
        while ((x = unlinkLast()) == null) {
            if (nanos <= 0)
                return null;
            nanos = notEmpty.awaitNanos(nanos);
        }
        return x;
    } finally {
        lock.unlock();
    }
}

    public E getFirst() —— 获取首个 —— 从当前链接阻塞双端队列的头部获取元素。该方法是头部检查方法中“异常”形式的实现,当链接阻塞双端队列存在元素时返回头元素;否则抛出无如此元素异常。

    方法直接调用peekFirst()方法实现。

/**
 * @throws NoSuchElementException {@inheritDoc} 无如此元素异常
 * @Description: 名称:获取首个
 * @Description: 作用:从当前链接阻塞双端队列的头部获取元素。该方法是头部检查方法中“异常”形式的实现,当链接阻塞双端队列存在元素
 * @Description: 时返回头元素;否则抛出无如此元素异常。
 * @Description: 逻辑:方法直接调用peekFirst()方法实现。
 */
public E getFirst() {
    E x = peekFirst();
    if (x == null) throw new NoSuchElementException();
    return x;
}

    public E getLast() —— 获取首个 —— 从当前链接阻塞双端队列的尾部获取元素。该方法是尾部检查方法中“异常”形式的实现,当链接阻塞双端队列存在元素时返回尾元素;否则抛出无如此元素异常。

    方法直接调用peekLast()方法实现。

/**
 * @throws NoSuchElementException {@inheritDoc} 无如此元素异常
 * @Description: 名称:获取首个
 * @Description: 作用:从当前链接阻塞双端队列的尾部获取元素。该方法是尾部检查方法中“异常”形式的实现,当链接阻塞双端队列存在元素
 * @Description: 时返回尾元素;否则抛出无如此元素异常。
 * @Description: 逻辑:方法直接调用peekLast()方法实现。
 */
public E getLast() {
    E x = peekLast();
    if (x == null) throw new NoSuchElementException();
    return x;
}

    public E peekFirst() —— 窥视首个 —— 从当前链接阻塞双端队列的头部获取元素。该方法是头部检查方法中“特殊值”形式的实现,当链接阻塞双端队列存在元素时返回头元素;否则返回null。

    方法在悲观锁的保护下,判断头节点是否为null。是则直接返回null;否则返回头节点的元素。

/**
 * @Description: 名称:窥视首个
 * @Description: 作用:从当前链接阻塞双端队列的头部获取元素。该方法是头部检查方法中“特殊值”形式的实现,当链接阻塞双端队列存在元
 * @Description: 素时返回头元素;否则返回null。
 * @Description: 逻辑:方法在悲观锁的保护下,判断头节点是否为null。是则直接返回null;否则返回头节点的元素。
 */
public E peekFirst() {
    final ReentrantLock lock = this.lock;
    lock.lock();
    try {
        return (first == null) ? null : first.item;
    } finally {
        lock.unlock();
    }
}

    public E peekLast() —— 窥视最后 —— 从当前链接阻塞双端队列的尾部获取元素。该方法是尾部检查方法中“特殊值”形式的实现,当链接阻塞双端队列存在元素时返回尾元素;否则返回null。

    方法在悲观锁的保护下,判断尾节点是否为null。是则直接返回null;否则返回尾节点的元素。

/**
 * @Description: 名称:窥视最后
 * @Description: 作用:从当前链接阻塞双端队列的尾部获取元素。该方法是尾部检查方法中“特殊值”形式的实现,当链接阻塞双端队列存在元
 * @Description: 素时返回尾元素;否则返回null。
 * @Description: 逻辑:方法在悲观锁的保护下,判断尾节点是否为null。是则直接返回null;否则返回尾节点的元素。
 */
public E peekLast() {
    final ReentrantLock lock = this.lock;
    lock.lock();
    try {
        return (last == null) ? null : last.item;
    } finally {
        lock.unlock();
    }
}

    public boolean removeFirstOccurrence(Object o) —— 移除首次出现 —— 从当前链接阻塞双端队列中移除指定元素的首个单例,移除成功返回true;否则返回false。

    方法在悲观锁的保护下,从头节点开始向后遍历。如果找到首个指定元素所在的节点则通过断开链接的方式将之移除并返回true;否则返回false。

/**
 * @Description: 名称:移除首次出现
 * @Description: 作用:从当前链接阻塞双端队列中移除指定元素的首个单例,移除成功返回true;否则返回false。
 * @Description: 逻辑:方法在悲观锁的保护下,从头节点开始向后遍历。如果找到首个指定元素所在的节点则通过断开链接的方式将之移除并
 * @Description: 返回true;否则返回false。
 */
public boolean removeFirstOccurrence(Object o) {
    if (o == null) return false;
    final ReentrantLock lock = this.lock;
    lock.lock();
    try {
        for (Node<E> p = first; p != null; p = p.next) {
            if (o.equals(p.item)) {
                unlink(p);
                return true;
            }
        }
        return false;
    } finally {
        lock.unlock();
    }
}

    public boolean removeLastOccurrence(Object o) —— 移除最后出现 —— 从当前链接阻塞双端队列中移除指定元素的最后单例,移除成功返回true;否则返回false。

    方法在悲观锁的保护下,从尾节点开始向前遍历。如果找到最后指定元素所在的节点则通过断开链接的方式将之移除并返回true;否则返回false。

/**
 * @Description: 名称:移除最后出现
 * @Description: 作用:从当前链接阻塞双端队列中移除指定元素的最后单例,移除成功返回true;否则返回false。
 * @Description: 逻辑:方法在悲观锁的保护下,从尾节点开始向前遍历。如果找到最后指定元素所在的节点则通过断开链接的方式将之移除并
 * @Description: 返回true;否则返回false。
 */
public boolean removeLastOccurrence(Object o) {
    if (o == null) return false;
    final ReentrantLock lock = this.lock;
    lock.lock();
    try {
        // 从后向前遍历,获取最后一个元素相等的节点。
        for (Node<E> p = last; p != null; p = p.prev) {
            if (o.equals(p.item)) {
                unlink(p);
                return true;
            }
        }
        return false;
    } finally {
        lock.unlock();
    }
}

    public boolean add(E e) —— 新增 —— 向当前链接阻塞双端队列的尾部插入指定元素。该方法是插入/放置方法“异常”形式的实现,当链接阻塞双端队列存在剩余容量时插入/放置成功并返回true;否则抛出非法状态异常。

    方法直接调用 addLast(E e)方法实现。

/**
 * Inserts the specified element at the end of this deque unless it would violate capacity restrictions.  When using a capacity-restricted deque, it
 * is generally preferable to use method {@link #offer(Object) offer}.
 * 在当前双端队列的尾部插入指定元素除非它违反容量限制。当使用一个容量受限的双端队列时它通常使用offer(Object)方法。
 * <p>
 * This method is equivalent to {@link #addLast}.
 * 当前方法等价于addLast(E e)方法。
 *
 * @throws IllegalStateException if this deque is full
 *                               非法状态异常:如果当前双端队列已满
 * @throws NullPointerException  if the specified element is null
 *                               空指针异常:如果指定元素为null
 * @Description: 名称:新增
 * @Description: 作用:向当前链接阻塞双端队列的尾部插入指定元素。该方法是插入/放置方法“异常”形式的实现,当链接阻塞双端队列存在剩
 * @Description: 余容量时插入/放置成功并返回true;否则抛出非法状态异常。
 * @Description: 逻辑:方法直接调用 addLast(E e)方法实现。
 */
public boolean add(E e) {
    addLast(e);
    return true;
}

    public boolean offer(E e) —— 新增 —— 向当前链接阻塞双端队列的尾部插入指定元素。该方法是插入/放置方法“特殊值”形式的实现,当链接阻塞双端队列存在剩余容量时插入/放置成功并返回true;否则返回false。

    方法直接调用offerLast(E e)方法实现。

/**
 * @throws NullPointerException if the specified element is null
 *                              空指针异常:如果指定元素为null
 * @Description: 名称:新增
 * @Description: 作用:向当前链接阻塞双端队列的尾部插入指定元素。该方法是插入/放置方法“特殊值”形式的实现,当链接阻塞双端队列存在
 * @Description: 剩余容量时插入/放置成功并返回true;否则返回false。
 * @Description: 逻辑:方法直接调用offerLast(E e)方法实现。
 */
public boolean offer(E e) {
    return offerLast(e);
}

    public void put(E e) throws InterruptedException —— 放置 —— 向当前链接阻塞双端队列的尾部插入指定元素。该方法是插入/放置方法“阻塞”形式的实现,当链接阻塞双端队列存在剩余容量时插入/放置成功;否则等待至存在剩余容量为止。

    方法直接调用putLast(E e)方法实现。

/**
 * @throws NullPointerException {@inheritDoc} 空指针异常
 * @throws InterruptedException {@inheritDoc} 中断异常
 * @Description: 名称:放置
 * @Description: 作用:向当前链接阻塞双端队列的尾部插入指定元素。该方法是插入/放置方法“阻塞”形式的实现,当链接阻塞双端队列存在剩
 * @Description: 余容量时插入/放置成功;否则等待至存在剩余容量为止。
 * @Description: 逻辑:方法直接调用putLast(E e)方法实现。
 */
public void put(E e) throws InterruptedException {
    putLast(e);
}

    public boolean offer(E e, long timeout, TimeUnit unit) throws InterruptedException —— 提供 —— 向当前链接阻塞双端队列的尾部插入指定元素。该方法是插入/放置方法“超时”形式的实现,当链接阻塞双端队列存在剩余容量时插入/放置成功并返回true;否则在指定等待时间内等待至存在剩余容量,超出指定等待时间则返回false。

    方法直接调用offerLast(E e, long timeout, TimeUnit unit)方法实现。

/**
 * @throws NullPointerException {@inheritDoc} 空指针异常
 * @throws InterruptedException {@inheritDoc} 中断异常
 * @Description: 名称:提供
 * @Description: 作用:向当前链接阻塞双端队列的尾部插入指定元素。该方法是插入/放置方法“超时”形式的实现,当链接阻塞双端队列存在剩
 * @Description: 余容量时插入/放置成功并返回true;否则在指定等待时间内等待至存在剩余容量,超出指定等待时间则返回false。
 * @Description: 逻辑:方法直接调用offerLast(E e, long timeout, TimeUnit unit)方法实现。
 */
public boolean offer(E e, long timeout, TimeUnit unit) throws InterruptedException {
    return offerLast(e, timeout, unit);
}

    public E remove() —— 移除 —— 从当前链接阻塞双端队列的头部移除并获取元素。该方法是移除/拿取方法中“异常”形式的实现,当链接阻塞双端队列存在元素时移除/拿取并返回头元素;否则抛出无如此元素异常。

    方法直接调用removeFirst()方法实现。

/**
 * Retrieves and removes the head of the queue represented by this deque. This method differs from {@link #poll poll} only in that it throws an
 * exception if this deque is empty.
 * 检索并移除当前双端队列所代表的头元素。当前方法不同于poll()方法,如果当前双端队列为空时其只能抛出异常。
 * <p>
 * This method is equivalent to {@link #removeFirst() removeFirst}.
 * 当前方法等价于removeFirst()方法。
 *
 * @return the head of the queue represented by this deque  当前双端队列所代表的头元素
 * @throws NoSuchElementException if this deque is empty
 *                                无如此元素异常:如果当前双端队列为空
 * @Description: 名称:移除
 * @Description: 作用:从当前链接阻塞双端队列的头部移除并获取元素。该方法是移除/拿取方法中“异常”形式的实现,当链接阻塞双端队列存
 * @Description: 在元素时移除/拿取并返回头元素;否则抛出无如此元素异常。
 * @Description: 逻辑:方法直接调用removeFirst()方法实现。
 */
public E remove() {
    return removeFirst();
}

    public E poll() —— 轮询 —— 从当前链接阻塞双端队列的头部移除并获取元素。该方法是移除/拿取方法中“特殊值”形式的实现,当链接阻塞双端队列存在元素时移除/拿取并返回头元素;否则返回null。

    方法直接调用pollFirst()方法实现。

/**
 * @Description: 名称:轮询
 * @Description: 作用:从当前链接阻塞双端队列的头部移除并获取元素。该方法是移除/拿取方法中“特殊值”形式的实现,当链接阻塞双端队列
 * @Description: 存在元素时移除/拿取并返回头元素;否则返回null。
 * @Description: 逻辑:方法直接调用pollFirst()方法实现。
 */
public E poll() {
    return pollFirst();
}

    public E take() throws InterruptedException —— 拿取 —— 从当前链接阻塞双端队列的头部移除并获取元素。该方法是移除/拿取方法中“阻塞”形式的实现,当链接阻塞双端队列存在元素时移除/拿取并返回头元素;否则等待至存在元素。

    方法直接调用takeFirst()方法实现。

/**
 * @Description: 名称:拿取
 * @Description: 作用:从当前链接阻塞双端队列的头部移除并获取元素。该方法是移除/拿取方法中“阻塞”形式的实现,当链接阻塞双端队列存
 * @Description: 在元素时移除/拿取并返回头元素;否则等待至存在元素。
 * @Description: 逻辑:方法直接调用takeFirst()方法实现。
 */
public E take() throws InterruptedException {
    return takeFirst();
}

    public E poll(long timeout, TimeUnit unit) throws InterruptedException —— 轮询 —— 从当前链接阻塞双端队列的头部移除并获取元素。该方法是移除/拿取方法中“超时”形式的实现,当链接阻塞双端队列存在元素时移除/拿取并返回头元素;否则在指定等待时间内等待至存在元素,超出指定等待时间则返回null。

    方法直接调用pollFirst(long timeout, TimeUnit unit)方法实现。

/**
 * @Description: 名称:轮询
 * @Description: 作用:从当前链接阻塞双端队列的头部移除并获取元素。该方法是移除/拿取方法中“超时”形式的实现,当链接阻塞双端队列存
 * @Description: 在元素时移除/拿取并返回头元素;否则在指定等待时间内等待至存在元素,超出指定等待时间则返回null。
 * @Description: 逻辑:方法直接调用pollFirst(long timeout, TimeUnit unit)方法实现。
 */
public E poll(long timeout, TimeUnit unit) throws InterruptedException {
    return pollFirst(timeout, unit);
}

    public E element() —— 元素 —— 从当前链接阻塞双端队列的头部获取元素。该方法是头部检查方法中“异常”形式的实现,当链接阻塞双端队列存在元素时返回头元素;否则抛出无如此元素异常。

    方法直接调用getFirst()方法实现。

/**
 * Retrieves, but does not remove, the head of the queue represented by this deque.  This method differs from {@link #peek peek} only in that it
 * throws an exception if this deque is empty.
 * 紧缩,但不移除当前双端队列代表的队列的头元素。该方法不同于peek()方法,其在当前双端队列为空时只会抛出异常。
 * <p>
 * This method is equivalent to {@link #getFirst() getFirst}.
 * 当前方法等价于getFirst()方法。
 *
 * @return the head of the queue represented by this deque 当前双端队列代表的队列的头元素
 * @throws NoSuchElementException if this deque is empty
 *                                无如此元素异常:如果当前双端队列为空
 * @Description: 名称:元素
 * @Description: 作用:从当前链接阻塞双端队列的头部获取元素。该方法是头部检查方法中“异常”形式的实现,当链接阻塞双端队列存在元素
 * @Description: 时返回头元素;否则抛出无如此元素异常。
 * @Description: 逻辑:方法直接调用getFirst()方法实现。
 */
public E element() {
    return getFirst();
}

    public E peek() —— 窥视 —— 从当前链接阻塞双端队列的头部获取元素。该方法是头部检查方法中“特殊值”形式的实现,当链接阻塞双端队列存在元素时返回头元素;否则返回null。

    方法直接调用peekFirst()方法实现。

/**
 * @Description: 名称:窥视
 * @Description: 作用:从当前链接阻塞双端队列的头部获取元素。该方法是头部检查方法中“特殊值”形式的实现,当链接阻塞双端队列存在元
 * @Description: 素时返回头元素;否则返回null。
 * @Description: 逻辑:方法直接调用peekFirst()方法实现。
 */
public E peek() {
    return peekFirst();
}

    public int remainingCapacity() —— 剩余容量 —— 获取当前链接阻塞双端队列的剩余容量。

    方法在悲观锁的保护下,使用当前链接阻塞双端队列的初始容量减去大小来计算剩余容量。

/**
 * Returns the number of additional elements that this deque can ideally (in the absence of memory or resource constraints) accept without
 * blocking. This is always equal to the initial capacity of this deque less the current {@code size} of this deque.
 * 返回当前双端队列理论上不阻塞可以接受新增的元素数量(不考虑内存与资源限制的情况)。其永远等于当前双端队列的初始容量减去当前
 * 双端队列的当前大小。
 * <p>
 * Note that you <em>cannot</em> 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.
 * 注意:你不能经常通过检查remainingCapacity来试图成功插入一个元素,因为它可能处于被其它线程插入或移除元素的情况。
 *
 * @Description: 名称:剩余容量
 * @Description: 作用:获取当前链接阻塞双端队列的剩余容量。
 * @Description: 逻辑:方法在悲观锁的保护下,使用当前链接阻塞双端队列的初始容量减去大小来计算剩余容量。
 */
public int remainingCapacity() {
    final ReentrantLock lock = this.lock;
    lock.lock();
    try {
        return capacity - count;
    } finally {
        lock.unlock();
    }
}

    public int drainTo(Collection<? super E> c) —— 流失 —— 将当前链接阻塞双端队列中的所有元素迁移至指定集中,并返回迁移的元素总数。

    方法直接调用drainTo(Collection<? super E> c, int maxElements)方法实现。

/**
 * @throws UnsupportedOperationException {@inheritDoc} 不支持操作异常
 * @throws ClassCastException            {@inheritDoc} 类转换异常
 * @throws NullPointerException          {@inheritDoc} 空指针异常
 * @throws IllegalArgumentException      {@inheritDoc} 非法参数异常
 * @Description: 名称:流失
 * @Description: 作用:将当前链接阻塞双端队列中的所有元素迁移至指定集中,并返回迁移的元素总数。
 * @Description: 逻辑:方法直接调用drainTo(Collection<? super E> c, int maxElements)方法实现。
 */
public int drainTo(Collection<? super E> c) {
    return drainTo(c, Integer.MAX_VALUE);
}

    public int drainTo(Collection<? super E> c, int maxElements) —— 流失 —— 将当前链接阻塞双端队列中的至多指定数量的元素迁移至指定集中,并返回迁移的元素总数。

    方法在悲观锁的保护下计算迁移的元素数量,在当前的元素数量与参数数量之前去最小值。随后从头节点开始遍历,将元素依次加入指定集中,直至到达目标数量。注意,元素的获取、加入指定集与元素节点的移除是分两次进行的,这是为了防止元素丢失的情况。如果元素先从当前链接阻塞双端队列中移除再插入指定集,则如果插入失败而抛出异常,则就会导致当前元素的丢失,即既不存在于当前链接阻塞双端队列中,也不存在于指定集中。

/**
 * @throws UnsupportedOperationException {@inheritDoc} 不支持操作异常
 * @throws ClassCastException            {@inheritDoc} 类转换异常
 * @throws NullPointerException          {@inheritDoc} 空指针异常
 * @throws IllegalArgumentException      {@inheritDoc} 非法参数异常
 * @Description: 名称:流失
 * @Description: 作用:将当前链接阻塞双端队列中的至多指定数量的元素迁移至指定集中,并返回迁移的元素总数。
 * @Description: 逻辑:方法在悲观锁的保护下计算迁移的元素数量,在当前的元素数量与参数数量之前去最小值。随后从头节点开始遍历,
 * @Description: 将元素依次加入指定集中,直至到达目标数量。注意,元素的获取、加入指定集与元素节点的移除是分两次进行的,这是
 * @Description: 为了防止元素丢失的情况。如果元素先从当前链接阻塞双端队列中移除再插入指定集,则如果插入失败而抛出异常,则就
 * @Description: 会导致当前元素的丢失,即既不存在于当前链接阻塞双端队列中,也不存在于指定集中。
 */
public int drainTo(Collection<? super E> c, int maxElements) {
    if (c == null)
        throw new NullPointerException();
    if (c == this)
        throw new IllegalArgumentException();
    if (maxElements <= 0)
        return 0;
    final ReentrantLock lock = this.lock;
    lock.lock();
    try {
        int n = Math.min(maxElements, count);
        for (int i = 0; i < n; i++) {
            c.add(first.item);   // In this order, in case add() throws.
            unlinkFirst();
        }
        return n;
    } finally {
        lock.unlock();
    }
}

    public void push(E e) —— 推送 —— 向当前链接阻塞双端队列的头部插入指定元素。如果存在剩余空间则插入成功,否则抛出非法状态异常。

    方法直接调用addFirst(E e)方法完成。

/**
 * @throws IllegalStateException if this deque is full
 *                               非法状态异常:如果当前双端队列已满溢
 * @throws NullPointerException  {@inheritDoc} 空指针异常
 * @Description: 名称:推送
 * @Description: 作用:向当前链接阻塞双端队列的头部插入指定元素。如果存在剩余空间则插入成功,否则抛出非法状态异常。
 * @Description: 逻辑:方法直接调用addFirst(E e)方法完成。
 */
public void push(E e) {
    addFirst(e);
}

    public E pop() —— 弹出 —— 从当前链接双端队列的头部移除并获取元素。如果存在元素则返回头元素,否则抛出无如此元素异常。

    方法直接调用removeFirst方法实现。

/**
 * @throws NoSuchElementException {@inheritDoc} 无如此元素异常
 * @Description: 名称:弹出
 * @Description: 作用:从当前链接双端队列的头部移除并获取元素。如果存在元素则返回头元素,否则抛出无如此元素异常。
 * @Description: 逻辑:方法直接调用removeFirst方法实现。
 */
public E pop() {
    return removeFirst();
}

    public boolean remove(Object o) —— 移除首次出现 —— 从当前链接阻塞双端队列中移除指定元素的首个单例,移除成功返回true;否则返回false。

    方法直接调用removeFirstOccurrence(Object o)方法实现。

/**
 * Removes the first occurrence of the specified element from this deque. If the deque does not contain the element, it is unchanged. More
 * formally, removes the first element {@code e} such that {@code o.equals(e)} (if such an element exists). Returns {@code true} if this deque
 * contained the specified element (or equivalently, if this deque changed as a result of the call).
 * 从双端队列中移除指定元素的首次出现「的对象」。如果双端队列不包含元素,它是未改变的。更正式的,通过o.equals(e)}以移除首个
 * 元素(如果如此元素存在)。如果当前双端队列包含指定元素(或等效地,如果调用的结果是当前双端队列发生改变)则返回true。
 * <p>
 * This method is equivalent to {@link #removeFirstOccurrence(Object) removeFirstOccurrence}.
 * 当前方法等价于removeFirstOccurrence(Object o)方法。
 *
 * @param o element to be removed from this deque, if present 用于从当前双端队列中移除的元素
 * @return {@code true} if this deque changed as a result of the call 如果调用的结果是当前双端队列发生改变
 * @Description: 名称:移除首次出现
 * @Description: 作用:从当前链接阻塞双端队列中移除指定元素的首个单例,移除成功返回true;否则返回false。
 * @Description: 逻辑:方法直接调用removeFirstOccurrence(Object o)方法实现。
 */
public boolean remove(Object o) {
    return removeFirstOccurrence(o);
}

    public int size() —— 大小 —— 获取当前链接阻塞双端队列中的元素总数。

    方法在悲观锁的保护下返回count(总数)字段记录的值。

/**
 * Returns the number of elements in this deque.
 * 返回当前双端队列中的元素数量。
 *
 * @return the number of elements in this deque  当前双端队列中的元素数量
 * @Description: 名称:大小
 * @Description: 作用:获取当前链接阻塞双端队列中的元素总数。
 * @Description: 逻辑:方法在悲观锁的保护下返回count(总数)字段记录的值。
 */
public int size() {
    final ReentrantLock lock = this.lock;
    lock.lock();
    try {
        return count;
    } finally {
        lock.unlock();
    }
}

    public boolean contains(Object o) —— 包含 —— 判断当前阻塞双端队列中是否包含指定元素,是则返回true;否则返回false。

    方法在悲观锁的保护下从头节点向后遍历。如果在遍历过程中发现了指定元素则返回true;否则返回false。

/**
 * Returns {@code true} if this deque contains the specified element. More formally, returns {@code true} if and only if this deque contains
 * at least one element {@code e} such that {@code o.equals(e)}.
 * 如果当前双端队列包含指定元素则返回true。更正式地,当且仅当当前双端队列通过o.equals(e)判断当前栓导管队列至少包含一个元素
 * 则返回true。
 *
 * @param o object to be checked for containment in this deque 用于在当前双端队列中检查包含的对象
 * @return {@code true} if this deque contains the specified element 如果当前双端队列包含指定元素则返回true
 * @Description: 名称:包含
 * @Description: 作用:判断当前阻塞双端队列中是否包含指定元素,是则返回true;否则返回false。
 * @Description: 逻辑:方法在悲观锁的保护下从头节点向后遍历。如果在遍历过程中发现了指定元素则返回true;否则返回false。
 */
public boolean contains(Object o) {
    if (o == null) return false;
    final ReentrantLock lock = this.lock;
    lock.lock();
    try {
        for (Node<E> p = first; p != null; p = p.next)
            if (o.equals(p.item))
                return true;
        return false;
    } finally {
        lock.unlock();
    }
}

    public Object[] toArray() —— 转化数组 —— 获取一个按迭代器顺序包含当前链接阻塞双端队列中所有元素的数组。

    方法在悲观锁的保护下分配一个长度与当前链接阻塞双端队列元素总数相同的数组,并从头节点向后遍历,将元素依次加入新数组中,最后返回。

/**
 * Returns an array containing all of the elements in this deque, in proper sequence (from first to last element).
 * 返回一个按合适的顺序(从首个元素到最后元素)包含当前双端队列中所有元素的数组。
 * <p>
 * The returned array will be "safe" in that no references to it are maintained by this deque.  (In other words, this method must allocate a
 * new array).  The caller is thus free to modify the returned array.
 * 返回的数组将是安全的,当前双端队列中没有维护任何指向它的引用。(换话说话,当前方法必须分配一个新的数组)。调用者因此
 * 可以自由修改返回的数组。
 * <p>
 * This method acts as bridge between array-based and collection-based APIs.
 * 当前方法行为可以作为基于数组和基于集的API之间的桥梁。
 *
 * @return an array containing all of the elements in this deque  包含当前双端队列中所有元素的数组
 * @Description: 名称:转化数组
 * @Description: 作用:获取一个按迭代器顺序包含当前链接阻塞双端队列中所有元素的数组。
 * @Description: 逻辑:方法在悲观锁的保护下分配一个长度与当前链接阻塞双端队列元素总数相同的数组,并从头节点向后遍历,将元
 * @Description: 素依次加入新数组中,最后返回。
 */
@SuppressWarnings("unchecked")
public Object[] toArray() {
    final ReentrantLock lock = this.lock;
    lock.lock();
    try {
        Object[] a = new Object[count];
        int k = 0;
        for (Node<E> p = first; p != null; p = p.next)
            a[k++] = p.item;
        return a;
    } finally {
        lock.unlock();
    }
}

    public T[] toArray(T[] a) —— 转化数组 —— 获取一个按迭代器顺序包含当前链接阻塞双端队列中所有元素的泛型数组。如果参数泛型数组长度足以容纳所有元素,则令之承载所有元素后返回。并且如果参数泛型数组的长度大于当前链接阻塞双端队列的元素总数,则将已承载所有元素的参数泛型数组的size索引位置设置为null,表示从当前链接阻塞双端队列中承载的元素到此为止。当然,该方案只对不允许保存null元素的集有效。如果参数泛型数组的长度不足以承载所有元素,则重分配一个相同泛型且长度与当前链接阻塞双端队列元素总数相同的新泛型数组以承接所有元素后返回。

    方法在悲观锁的保护下判断参数泛型数组是否合法。合法则将参数泛型数组作为目标数组;不合法则分配一个相同泛型且长度与当前链接阻塞双端队列的元素总数相同的新泛型数组作为目标泛型数组。虽然从头遍历当前链接阻塞双端队列,将元素依次加入目标泛型数组中。如果目标泛型数组的长度大于当前链接阻塞双端队列的元素总数,则将目标泛型数组的size索引位置设置为null,表示承载的元素到此位置。最后返回目标泛型数组。

/**
 * Returns an array containing all of the elements in this deque, in proper sequence; the runtime type of the returned array is that of
 * the specified array.  If the deque 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 deque.
 * 返回一个按合适的顺序,包含当前双端队列中所有元素的数组;返回数组的运行时类型为指定数组「的运行时类型」。如果双端队列
 * 与 指定数组符合,直接在其中返回。否则,分配一个运行时类型与指定数组相同且大小与当前双端队列相同的新数组。
 * <p>
 * If this deque fits in the specified array with room to spare (i.e., the array has more elements than this deque), the element in the array
 * immediately following the end of the deque is set to {@code null}.
 * 如果当前双端队列符合指定数组,并且存在多余空间(例如:数组比当前双端队列有更多的元素),数组中的元素紧接着集结束之后
 * 设置为null。
 * <p>
 * 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.
 * 类似于toArray()方法,当前方法行为可作为基于数组和基于集的API之间的桥梁。进一步的,当前方法允许精确控制输出数组的类型,
 * 并可能,在某些情况下,被用于节省分配花费。
 * <p>
 * Suppose {@code x} is a deque known to contain only strings. The following code can be used to dump the deque into a newly allocated
 * array of {@code String}:
 * 假设x是已知只包含字符串的一个双端队列。下面的代码能够用于倾泻双端队列至一个新分配的字符串数组中:
 * <pre> {@code String[] y = x.toArray(new String[0]);}</pre>
 * <p>
 * Note that {@code toArray(new Object[0])} is identical in function to {@code toArray()}.
 * 注意:toArray(new Object[0])在功能上等同于toArray()。
 *
 * @param a the array into which the elements of the deque 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 deque 一个包含当前双端队列所有元素的数组
 * @throws ArrayStoreException  if the runtime type of the specified array is not a supertype of the runtime type of every element in this
 *                              deque
 *                              数组保存异常如果指定数组的运行时类型不是每个当前双端队列中每个元素运行时类型的父类型、
 * @throws NullPointerException if the specified array is null
 *                              空指针异常:如果指定数组为null
 * @Description: 名称:转化数组
 * @Description: 作用:获取一个按迭代器顺序包含当前链接阻塞双端队列中所有元素的泛型数组。如果参数泛型数组长度足以容纳所
 * @Description: 有元素,则令之承载所有元素后返回。并且如果参数泛型数组的长度大于当前链接阻塞双端队列的元素总数,则将已
 * @Description: 承载所有元素的参数泛型数组的size索引位置设置为null,表示从当前链接阻塞双端队列中承载的元素到此为止。当然,
 * @Description: 该方案只对不允许保存null元素的集有效。如果参数泛型数组的长度不足以承载所有元素,则重分配一个相同泛型且长
 * @Description: 度与当前链接阻塞双端队列元素总数相同的新泛型数组以承接所有元素后返回。
 * @Description: 逻辑:方法在悲观锁的保护下判断参数泛型数组是否合法。合法则将参数泛型数组作为目标数组;不合法则分配一个
 * @Description: 相同泛型且长度与当前链接阻塞双端队列的元素总数相同的新泛型数组作为目标泛型数组。虽然从头遍历当前链接阻
 * @Description: 塞双端队列,将元素依次加入目标泛型数组中。如果目标泛型数组的长度大于当前链接阻塞双端队列的元素总数,则
 * @Description: 将目标泛型数组的size索引位置设置为null,表示承载的元素到此位置。最后返回目标泛型数组。
 */
@SuppressWarnings("unchecked")
public <T> T[] toArray(T[] a) {
    final ReentrantLock lock = this.lock;
    lock.lock();
    try {
        if (a.length < count)
            a = (T[]) java.lang.reflect.Array.newInstance
                    (a.getClass().getComponentType(), count);
        int k = 0;
        for (Node<E> p = first; p != null; p = p.next)
            a[k++] = (T) p.item;
        if (a.length > k)
            a[k] = null;
        return a;
    } finally {
        lock.unlock();
    }
}

    public void clear() —— 清理 —— 移除当前链接阻塞双端队列中的所有元素。

    方法在悲观锁的保护下从头遍历,移除每个节点的同时断开元素、前驱引用及后继引用等所有资源以便于GC回收。最后唤醒所有等待中的放置者执行插入/放置操作。

/**
 * Atomically removes all of the elements from this deque. The deque will be empty after this call returns.
 * 原子地从当前双端队列中移除所有元素。当前调用返回后双端队列应该为空。
 *
 * @Description: 名称:清理
 * @Description: 作用:移除当前链接阻塞双端队列中的所有元素。
 * @Description: 逻辑:方法在悲观锁的保护下从头遍历,移除每个节点的同时断开元素、前驱引用及后继引用等所有资源以便于GC
 * @Description: 回收。最后唤醒所有等待中的放置者执行插入/放置操作。
 */
public void clear() {
    final ReentrantLock lock = this.lock;
    lock.lock();
    try {
        for (Node<E> f = first; f != null; ) {
            f.item = null;
            Node<E> n = f.next;
            f.prev = null;
            f.next = null;
            f = n;
        }
        first = last = null;
        count = 0;
        notFull.signalAll();
    } finally {
        lock.unlock();
    }
}

    public Iterator iterator() —— 迭代器 —— 获取一个当前链接阻塞双端队列的正序(从头到尾)迭代器

    方法直接创建一个正序迭代器返回。

/**
 * Returns an iterator over the elements in this deque in proper sequence. The elements will be returned in order from first (head) to
 * last (tail).
 * 返回一个按合适顺序迭代当前双端队列中元素的迭代器。元素将按从头到尾的顺序返回。
 * <p>
 * The returned iterator is <a href="package-summary.html#Weakly"><i>weakly consistent</i></a>.
 * 返回的迭代器是弱一致性的。
 *
 * @return an iterator over the elements in this deque in proper sequence 按合适顺序迭代当前双端队列中元素的迭代器
 * @Description: 名称:迭代器
 * @Description: 作用:获取一个当前链接阻塞双端队列的正序(从头到尾)迭代器
 * @Description: 逻辑:方法直接创建一个正序迭代器返回。
 */
public Iterator<E> iterator() {
    return new Itr();
}

    public Iterator descendingIterator() —— 下降迭代器 —— 获取一个当前链接阻塞双端队列的倒序(从尾到头)迭代器。

    方法直接创建一个下降迭代器返回。

/**
 * Returns an iterator over the elements in this deque in reverse sequential order.  The elements will be returned in order from last (tail)
 * to first (head).
 * 返回一个按相反连续的顺序迭代当前双端队列中元素的迭代器。元素将按从尾到头的顺序返回。
 * <p>
 * The returned iterator is <a href="package-summary.html#Weakly"><i>weakly consistent</i></a>.
 * 返回的迭代器是弱一致性的。
 *
 * @return an iterator over the elements in this deque in reverse order 按相反顺序迭代当前双端队列中元素的迭代器
 * @Description: 名称:下降迭代器
 * @Description: 作用:获取一个当前链接阻塞双端队列的倒序(从尾到头)迭代器
 * @Description: 逻辑:方法直接创建一个下降迭代器返回。
 */
public Iterator<E> descendingIterator() {
    return new DescendingItr();
}

二 Node(节点)类源码及机制详解


 类

    Node(节点)类(下文简称节点)是链接阻塞双端队列的一个静态内部类,其即是元素的容器,也是底层链表的基本组成单位。与LinkedBlockingQueue(链接阻塞队列)类不同,链接阻塞双端队列的节点可同时保存前驱/后继引用,这使得链接阻塞双端队列支持正序/倒序两种迭代方式。

/**
 * Doubly-linked list node class
 * 双重链接列表节点类
 */
static final class Node<E> {
    
    ...
}

 字段

    item(项) —— 持有元素,当节点已被移除/拿取时为null。

/**
 * The item, or null if this node has been removed.
 * 项,或者如果当前节点已移除则返回null。
 *
 * @Description: 名称:项
 * @Description: 作用:持有元素,当节点已被移除/拿取时为null。
 * @Description: 逻辑:~
 */
E item;

    prev(前驱) —— 持有前驱节点的引用。如果为null,表示没有前驱节点,当前节点为链接阻塞双端队列的头节点;如果为自身,表示当前节点已从链接阻塞双端队列的尾部移除/拿取;否则说明持有的是真正的前驱节点。

/**
 * One of:
 * 一种:
 * - the real predecessor Node
 * - 真的前驱节点
 * - this Node, meaning the predecessor is tail
 * - 当前节点,意味着前驱节点为尾节点
 * - null, meaning there is no predecessor
 * - null,意味着没有前驱节点
 *
 * @Description: 名称:前驱
 * @Description: 作用:持有前驱节点的引用。如果为null,表示没有前驱节点,当前节点为链接阻塞双端队列的头节点;如果为自身,表示
 * @Description: 当前节点已从链接阻塞双端队列的尾部移除/拿取;否则说明持有的是真正的前驱节点。
 * @Description: 逻辑:~
 */
Node<E> prev;

    next(后继) —— 持有后继节点的引用。如果为null,表示没有后继节点,当前节点为链接阻塞双端队列的尾节点;如果为自身,表示当前节点已从链接阻塞双端队列的头部移除/拿取;否则说明持有的是真正的后继节点。

/**
 * One of:
 * 一种:
 * - the real successor Node
 * - 真实的后继节点
 * - this Node, meaning the successor is head
 * - 当前节点,意味着后继节点为头节点
 * - null, meaning there is no successor
 * - null,意味着没有后继节点
 *
 * @Description: 名称:后继
 * @Description: 作用:持有后继节点的引用。如果为null,表示没有后继节点,当前节点为链接阻塞双端队列的尾节点;如果为自身,表示
 * @Description: 当前节点已从链接阻塞双端队列的头部移除/拿取;否则说明持有的是真正的后继节点。
 * @Description: 逻辑:~
 */
Node<E> next;

 构造方法

    Node(E x) —— 创建一个包含指定元素的节点。

/**
 * @Description: 名称:~
 * @Description: 作用:创建一个包含指定元素的节点。
 * @Description: 逻辑:~
 */
Node(E x) {
    item = x;
}

三 AbstractItr(抽象迭代器)抽象类源码及机制详解


 类

    AbstractItr(抽象迭代器)抽象类是链接阻塞双端队列的一个静态内部类,同时也是链接阻塞双端队列迭代器的父/超类。链接阻塞双端队列拥有正序/倒序两类迭代器,两类迭代器除了迭代的顺序不同外在操作性质上是完全相同的,因此这些共同的部分就被抽象出来构成了 AbstractItr(抽象迭代器)抽象类,以避免两类迭代器在代码实现上的高度重复。

/**
 * Base class for Iterators for LinkedBlockingDeque
 * 链接阻塞双端队列的迭代器的基础类
 *
 * @Description: 名称:抽象迭代器
 * @Description: 作用:链接阻塞双端队列的正/倒序迭代器的父/超类
 * @Description: 逻辑:~
 */
private abstract class AbstractItr implements Iterator<E> {
    
    ...
    
}

 字段

    next(下个) —— 持有下次迭代返回的节点。

/**
 * The next node to return in next()
 * next()方法返回的下个节点
 *
 * @Description: 名称:下个
 * @Description: 作用:持有下次迭代返回的节点
 * @Description: 逻辑:~
 */
Node<E> next;

    nextItem(下个项) —— 持有下次迭代返回的节点的元素。由于节点从当前链接阻塞双端队列移除后会断开与元素的链接,因此元素需要单独保存。

/**
 * nextItem holds on to item fields because once we claim that an element exists in hasNext(), we must return item read under lock (
 * in advance()) even if it was in the process of being removed when hasNext() was called.
 * 下个项紧握项字段,因为我们曾经在hasNext()方法中声称存在元素,我们必须在锁下返回项用于读取,即使它在hasNext()方法
 * 调用结束后的过程中被移除。
 *
 * @Description: 名称:下个项
 * @Description: 作用:持有下次迭代返回的节点的元素。由于节点从当前链接阻塞双端队列移除后会断开与元素的链接,因此元
 * @Description: 素需要单独保存。
 * @Description: 逻辑:~
 */
E nextItem;

    lastRet(上个节点) —— 持有上次迭代返回的节点,用于迭代移除操作时使用。

/**
 * Node returned by most recent call to next. Needed by remove. Reset to null if this element is deleted by a call to remove.
 * 最近调用next()方法返回的节点。移除所需。如果元当前元素已通过调用remove()方法删除则重设为null。
 *
 * @Description: 名称:上个节点
 * @Description: 作用:持有上次迭代返回的节点,用于迭代移除操作时使用。
 * @Description: 逻辑:~
 */
private Node<E> lastRet;

 构造方法

    AbstractItr() —— 初始化一个链接阻塞双端队列的迭代器,设置指定顺序的首个节点与元素。

/**
 * @Description: 名称:~
 * @Description: 作用:初始化一个链接阻塞双端队列的迭代器,设置指定顺序的首个节点与元素。
 * @Description: 逻辑:~
 */
AbstractItr() {
    // set to initial position
    // 设置初始位置

    // 加锁。
    final ReentrantLock lock = LinkedBlockingDeque.this.lock;
    lock.lock();
    try {
        // 保存指定顺序的首个节点即其内部的元素。
        next = firstNode();
        nextItem = (next == null) ? null : next.item;
    } finally {
        lock.unlock();
    }
}

 方法

    abstract Node firstNode() —— 首个节点 —— 获取当前链接阻塞双端队列指定顺序下的首个节点。

/**
 * @Description: 名称:首个节点
 * @Description: 作用:获取当前链接阻塞双端队列指定顺序下的首个节点。
 * @Description: 逻辑:~
 */
abstract Node<E> firstNode();

    abstract Node<E> nextNode(Node<E> n) —— 下个节点 —— 获取当前链接阻塞双端队列中指定节点指定顺序下的下个节点。
/**
 * @Description: 名称:下个节点
 * @Description: 作用:获取当前链接阻塞双端队列中指定节点指定顺序下的下个节点。
 * @Description: 逻辑:~
 */
abstract Node<E> nextNode(Node<E> n);

    private Node succ(Node n) —— 后继 —— 在当前链接阻塞双端队列中按指定顺序找到指定节点的后继节点,注意:必须是存在于当前链接阻塞双端队列中的后继节点。

    方法按指定顺序向后遍历。如果后继节点不存在,说明指定节点已是指定顺序下的尾节点,直接返回null;如果后继节点存在且元素不为空,说明是位于当前链接阻塞双端队列中的后继节点,返回该后继节点;如果后继节点存在且与指定节点相同,说明指定节点已从当前链接阻塞双端队列中移除/拿取,返回当前链接阻塞双端队列中指定顺序下的头节点;如果后继节点存在且元素为空,说明后继节点同样已被内部移除(能够链接到一个已内部移除的节点,说明指定节点也是一个已被内部移除的节点),需要在当后继节点的基础上继续向后遍历。

/**
 * Returns the successor node of the given non-null, but possibly previously deleted, node.
 * 返回指定不为null,但是可能已删除的节点的后继节点。
 *
 * @Description: 名称:后继
 * @Description: 作用:在当前链接阻塞双端队列中按指定顺序找到指定节点的后继节点,注意:必须是存在于当前链接阻塞双端队
 * @Description: 列中的后继节点。
 * @Description: 逻辑:方法按指定顺序向后遍历。如果后继节点不存在,说明指定节点已是指定顺序下的尾节点,直接返回null;如
 * @Description: 果后继节点存在且元素不为空,说明是位于当前链接阻塞双端队列中的后继节点,返回该后继节点;如果后继节点
 * @Description: 存在且与指定节点相同,说明指定节点已从当前链接阻塞双端队列中移除/拿取,返回当前链接阻塞双端队列中指定
 * @Description: 顺序下的头节点;如果后继节点存在且元素为空,说明后继节点同样已被内部移除(能够链接到一个已内部移除的
 * @Description: 节点,说明指定节点也是一个已被内部移除的节点),需要在当后继节点的基础上继续向后遍历。
 */
private Node<E> succ(Node<E> n) {
    // Chains of deleted nodes ending in null or self-links are possible if multiple interior nodes are removed.
    // 如果多个内部节点被移除,则已删除节点链【外链】结束于null或自链接是可能的。

    for (; ; ) {
        // 获取指定节点在指定顺序下的后继节点。
        Node<E> s = nextNode(n);
        // 如果没有后继节点,则说明指定节点是指定顺序下的尾节点,直接结束循环并返回null。
        if (s == null)
            return null;
        else if (s.item != null)
            // 如果后继节点的元素不为null,说明是位于当前链接阻塞双端队列中的后继节点,返回该后继节点。
            return s;
        else if (s == n)
            // 如果后继节点与指定节点相同,说明指定节点已从当前链接阻塞双端队列中移除/拿取,应该返回指定顺序的首个节点。
            return firstNode();
        else
            // 如果后继节点存在,但元素为null,说明后继节点也已被内部移除,需要继续按指定顺序向后遍历。
            n = s;
    }
}

    void advance() —— 前进 —— 获取新的迭代节点及元素。

    方法在悲观锁的保护下,调用succ方法获取可用的后继节点作为新的迭代节点,并将之元素作为迭代元素。

/**
 * Advances next.
 * 前进下个
 *
 * @Description: 名称:前进
 * @Description: 作用:获取新的迭代节点及元素。
 * @Description: 逻辑:方法在悲观锁的保护下,调用succ方法获取可用的后继节点作为新的迭代节点,并将之元素作为迭代元素。
 */
void advance() {
    final ReentrantLock lock = LinkedBlockingDeque.this.lock;
    lock.lock();
    try {
        // assert next != null;
        next = succ(next);
        nextItem = (next == null) ? null : next.item;
    } finally {
        lock.unlock();
    }
}

    public boolean hasNext() —— 存在下个 —— 判断当前迭代器是否存在可迭代的元素,是则返回true;否则返回false。

    方法直接判断next字段是否为null,不为null则存在返回true,否则即不存在,返回false。

/**
 * @Description: 名称:存在下个
 * @Description: 作用:判断当前迭代器是否存在可迭代的元素,是则返回true;否则返回false。
 * @Description: 逻辑:方法直接判断next字段是否为null,不为null则存在返回true,否则即不存在,返回false。
 */
public boolean hasNext() {
    return next != null;
}

    public E next() —— 下个 —— 获取下个迭代元素。

    方法获取nextItem(下个项)字段中持有的元素用于返回,并将next(下个)字段中持有的节点设置为上个节点以备迭代移除使用。随后前进迭代器获取下个迭代节点及元素保存在next(下个)字段及nextItem(下个项)字段中。

/**
 * @Description: 名称:下个
 * @Description: 作用:获取下个迭代元素。
 * @Description: 逻辑:方法获取nextItem(下个项)字段中持有的元素用于返回,并将next(下个)字段中持有的节点设置为上个节
 * @Description: 点以备迭代移除使用。随后前进迭代器获取下个迭代节点及元素保存在next(下个)字段及nextItem(下个项)字段
 * @Description: 中。
 */
public E next() {
    if (next == null)
        throw new NoSuchElementException();
    lastRet = next;
    E x = nextItem;
    advance();
    return x;
}

    public void remove() —— 移除 —— 移除上个迭代元素。

    方法判断上个节点是否存在,如果不存在则说明没有可移除的元素,抛出异常。如果存在,则在悲观锁的保护下通过内部移除的方式将之从当前链接阻塞双端队列中移除。由于节点保存了前驱/后继引用,因此无需像链接阻塞队列一样先遍历找到指定节点的前驱节点才能断开链接。由于本质是内部移除,被移除的节点不会断开前驱/后继引用。

/**
 * @Description: 名称:移除
 * @Description: 作用:移除上个迭代元素。
 * @Description: 逻辑:方法判断上个节点是否存在,如果不存在则说明没有可移除的元素,抛出异常。如果存在,则在悲观锁的保护
 * @Description: 下通过内部移除的方式将之从当前链接阻塞双端队列中移除。由于节点保存了前驱/后继引用,因此无需像链接阻塞队
 * @Description: 列一样先遍历找到指定节点的前驱节点才能断开链接。由于本质是内部移除,被移除的节点不会断开前驱/后继引用。
 */
public void remove() {
    Node<E> n = lastRet;
    if (n == null)
        throw new IllegalStateException();
    lastRet = null;
    final ReentrantLock lock = LinkedBlockingDeque.this.lock;
    lock.lock();
    try {
        if (n.item != null)
            unlink(n);
    } finally {
        lock.unlock();
    }
}

    public boolean hasNext() —— 存在下个 —— 判断当前迭代器是否存在可迭代的元素,是则返回true;否则返回false。

    方法直接判断next字段是否为null,不为null则存在返回true,否则即不存在,返回false。

/**
 * @Description: 名称:存在下个
 * @Description: 作用:判断当前迭代器是否存在可迭代的元素,是则返回true;否则返回false。
 * @Description: 逻辑:方法直接判断next字段是否为null,不为null则存在返回true,否则即不存在,返回false。
 */
public boolean hasNext() {
    return next != null;
}

四 Itr(抽象迭代器)类源码及机制详解


 类

    正序迭代器。

/**
 * Forward iterator
 * 向前迭代器
 *
 * @Description: 名称:迭代器
 * @Description: 作用:正序迭代链接阻塞双端队列的元素。
 * @Description: 逻辑:~
 */
private class Itr extends AbstractItr {
    ...
}

 方法

    Node firstNode() —— 首个节点 —— 获取当前链接阻塞双端队列正序下的首个节点。

    方法直接返回头节点。

/**
 * @Description: 名称:首个节点
 * @Description: 作用:获取当前链接阻塞双端队列正序下的首个节点。
 * @Description: 逻辑:方法直接返回头节点。
 */
Node<E> firstNode() {
    // 获取正序的首个节点,即头节点。
    return first;
}

    Node nextNode(Node n) —— 下个节点 —— 获取当前链接阻塞双端队列中指定节点正序下的下个节点。

    方法直接返回指定节点的后继节点。

/**
 * @Description: 名称:下个节点
 * @Description: 作用:获取当前链接阻塞双端队列中指定节点正序下的下个节点。
 * @Description: 逻辑:方法直接返回指定节点的后继节点。
 */
Node<E> nextNode(Node<E> n) {
    return n.next;
}

五 DescendingItr(向后迭代器)类源码及机制详解


 类

    倒序迭代器。

/**
 * Descending iterator
 * 向后迭代器
 *
 * @Description: 名称:向后迭代器
 * @Description: 作用:倒序迭代链接阻塞双端队列的元素。
 * @Description: 逻辑:~
 */
private class DescendingItr extends AbstractItr {
    ...
}

 方法

    Node firstNode() —— 首个节点 —— 获取当前链接阻塞双端队列倒序下的首个节点。

    方法直接返回尾节点。

/**
 * @Description: 名称:首个节点
 * @Description: 作用:获取当前链接阻塞双端队列倒序下的首个节点。
 * @Description: 逻辑:方法直接返回尾节点。
 */
Node<E> firstNode() {
    return last;
}

    Node nextNode(Node n) —— 下个节点 —— 获取当前链接阻塞双端队列中指定节点正序下的下个节点。

    方法直接返回指定节点的前驱节点。

/**
 * @Description: 名称:下个节点
 * @Description: 作用:获取当前链接阻塞双端队列中指定节点正序下的下个节点。
 * @Description: 逻辑:方法直接返回指定节点的前驱节点。
 */
Node<E> nextNode(Node<E> n) {
    return n.prev;
}

六 相关系列


  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

说淑人

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

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

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

打赏作者

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

抵扣说明:

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

余额充值