Java LinkedBlockingDeque阻塞双端队列源码深度解析

390 阅读47分钟

一起养成写作习惯!这是我参与「掘金日新计划 · 4 月更文挑战」的第16天,点击查看活动详情

基于JDK1.8详细介绍了LinkedBlockingDeque的底层源码实现,包括双端队列的入队列、出队列、迭代等操作源码。实际上LinkedBlockingDeque的源码还是非常简单的!

1 LinkedBlockingDeque的概述

public class LinkedBlockingDeque< E > extends AbstractQueue< E > implements BlockingDeque< E >, Serializable

LinkedBlockingDeque来自于JDK1.5的JUC包,是一个支持并发操作的有界阻塞队列,底层数据结构是一个双链表,可以看作LinkedList的并发版本!

LinkedBlockingDeque实现了BlockingDeque接口,而BlockingDeque继承了BlockingQueue并对其进行了扩展,这使得 LinkedBlockingDeque不再局限于FIFO的传统队列元素存取模式,而是可以从队列的两端任意进行插入和移除元素,因此更加的灵活。

除了BlockingQueue的一套方法之外,额外增加了一套以First结尾的方法比如addFirst、removeFirst、getFirst等用于针对队列头部的插入、移除和检查操作,额外增加了一套以Last结尾的方法比如addLast、removeLast、getLast等用于针对队列尾部的插入、移除和检查操作。

和LinkedBlockingQueue一样,LinkedBlockingDeque同样作为有界队列,默认元素个数就是最大容量,即Integer.MAX_VALUE,也可以指定最大容量。

实现了Serializable接口,支持序列化,没有实现Cloneable,不支持克隆!

不支持null元素!

在这里插入图片描述

2 LinkedBlockingDeque的原理

2.1 主要属性

由于采用链表结构来保存数据,因此具有头、尾结点的引用first、last,链表结点类型是内部类Node类型。由于是一个有界队列,容量使用capacity变量来保存,capacity是int类型的,因此LinkedBlockingDeque的容量最大是Integer.MAX_VALUE。使用一个int类型的count来作为元素计数器。

具有一把ReentrantLock类型的锁lock,生产和消费线程都需要获取同一个锁。具有两个条件变量,notEmpty条件变量用于消费线程的阻塞和唤醒,notFull条件变量用于生产线程的阻塞和唤醒!

/**
 * 头结点,可以为null
 */
transient Node<E> first;

/**
 * 尾结点,可以为null
 */
transient Node<E> last;

/**
 * 队列元素计数器
 */
private transient int count;

/**
 * 队列的容量,初始化之后就不能变了
 */
private final int capacity;

/**
 * 生产、消费都需要获取的锁
 */
final ReentrantLock lock = new ReentrantLock();

/**
 * notEmpty条件对象,当队列为空时用于挂起消费线程
 */
private final Condition notEmpty = lock.newCondition();

/**
 * notFull条件对象,当队列已满时用于挂起生产线程
 */
private final Condition notFull = lock.newCondition();


/**
 * 双端队列的结点实现类
 */
static final class Node<E> {
    /**
     * 值域,如果结点被删除则item为null
     */
    E item;

    /**
     * 结点的前驱
     */
    Node<E> prev;

    /**
     * 结点的后继
     */
    Node<E> next;

    /**
     * 构造器
     *
     * @param x 元素值
     */
    Node(E x) {
        item = x;
    }
}

2.2 构造器

2.2.1 LinkedBlockingDeque()

public LinkedBlockingDeque()

创建一个容量为 Integer.MAX_VALUE 的 LinkedBlockingDeque。

/**
 * 创建一个容量为 Integer.MAX_VALUE 的 LinkedBlockingDeque。
 */
public LinkedBlockingDeque() {
    //调用另一个构造器,参数为Integer.MAX_VALUE
    this(Integer.MAX_VALUE);
}

2.2.2 LinkedBlockingDeque(capacity)

public LinkedBlockingDeque(int capacity)

创建一个具有指定容量的 LinkedBlockingDeque。如果 capacity 小于 1,则抛出IllegalArgumentException。

/**
 * 创建一个具有指定容量的 LinkedBlockingDeque。
 *
 * @param capacity 指定容量
 * @throws IllegalArgumentException 如果 capacity 小于 1
 */
public LinkedBlockingDeque(int capacity) {
    //capacity大小的校验
    if (capacity <= 0) throw new IllegalArgumentException();
    //capacity初始化为指定值
    this.capacity = capacity;
}

2.2.3 LinkedBlockingDeque( c )

public LinkedBlockingDeque(Collection<? extends E> c)

创建一个容量是 Integer.MAX_VALUE 的 LinkedBlockingDeque,包含指定集合的全部元素,元素按该集合迭代器的遍历顺序添加。

如果指定集合为或任意元素为null,则抛出NullPointerException。如果指定集合元素数量超过Integer.MAX_VALUE,那么抛出IllegalStateException。

/**
 * 创建一个容量是 Integer.MAX_VALUE 的 LinkedBlockingDeque,包含指定集合的全部元素,元素按该集合迭代器的遍历顺序添加。
 *
 * @param c 指定集合
 * @throws NullPointerException 如果指定集合为或任意元素为null
 */
public LinkedBlockingDeque(Collection<? extends E> c) {
    //调用另一个构造器,初始化容量为Integer.MAX_VALUE
    this(Integer.MAX_VALUE);
    final ReentrantLock lock = this.lock;
    //这里和LinkedBlockingQueue是一样的,需要加锁来保证数据的可见性,因为头、尾结点没有使用volatile修饰
    //获取锁
    lock.lock(); // Never contended, but necessary for visibility
    try {
        //遍历指定集合
        for (E e : c) {
            //null校验
            if (e == null)
                throw new NullPointerException();
            //调用linkLast将指定集合的元素添加到队列尾部
            if (!linkLast(new Node<E>(e)))
                //如果linkLast返回false,说明集合元素数量达到了最大容量,因此抛出异常
                throw new IllegalStateException("Deque full");
        }
    } finally {
        //释放锁
        lock.unlock();
    }
}

2.3 入队操作

2.3.1 入队尾

2.3.1.1 putLast(e)方法

public void putLast(E e)

将指定的元素插入此队列的尾部,如果该队列已满,则线程等待。

如果因为获取不到锁而在同步队列中等待的时候被中断则会继续等待,即不响应中断,如果因为队列满了在条件队列中等待的时候在其他线程调用signal、signalAll方法唤醒该线程之前就因为中断而被唤醒了,也会抛出InterruptedException。另外,如果指定元素为 null则抛出NullPointerException 异常。

很简单,大概步骤为:

  1. 指定元素e的null检测,通过之后新建Node结点传入指定元素e。
  2. 不可中断的等待获取锁,即不响应中断,获取不到就在该锁的同步队列中等待被唤醒,等待时被中断之后会继续尝试获取锁;
  3. 获取到锁之后,循环调用linkLast尝试将node结点加入队尾。如果linkLast返回false,表示队列满了加入失败,那么该线程在notFull条件队列中等待并释放锁,被唤醒之后会继续尝试获取锁、并继续循环调用linkLast。
  4. 如果linkLast返回true,表示加入成功,那么循环结束。
  5. 无论过程中发生了什么,最后的finally中解锁。
/**
 * 将指定的元素插入此队列的尾部,如果该队列已满,则线程等待。
 *
 * @throws NullPointerException 如果指定的元素为 null
 * @throws InterruptedException 如果因为获取不到锁而在同步队列中等待的时候被中断则会继续等待,即不响应中断,
 *                              如果因为队列满了在条件队列中等待的时候在其他线程调用signal、signalAll方法唤醒该线程之前就因为中断而被唤醒了,也会抛出InterruptedException。
 */
public void putLast(E e) throws InterruptedException {
    //e的校验
    if (e == null) throw new NullPointerException();
    //新建Node结点
    Node<E> node = new Node<E>(e);
    final ReentrantLock lock = this.lock;
    //不可中断的等待获取生产者锁,即不响应中断
    lock.lock();
    try {
        /*
         *循环调用linkLast尝试将node结点加入队尾
         */
        while (!linkLast(node))
            //如果失败,表示队列满了,那么该线程在notFull条件队列中等待并释放锁,被唤醒之后会继续尝试获取锁、并循环判断
            notFull.await();
    } finally {
        //释放锁
        lock.unlock();
    }
}
2.3.1.1.1 linkLast链接尾结点

linkLast用于将指定node结点链接到队列尾部成为新的尾结点,原理很简单就是在原尾结点last指向的结点后面新添加一个node结点,同时建立prev和next的引用关系。如果最开始队列为空,那么head和last都指向该node结点。

如果队列满了,那么直接返回false,如果链接成功,那么将会唤醒一个在notEmpty等待的消费线程,并返回true。

/**
 * 将指定结点链接到队列尾部成为新的尾结点
 *
 * @return true 成功 fasle 队列已满
 */
private boolean linkLast(Node<E> node) {
    // assert lock.isHeldByCurrentThread();
    //如果队列满了,那么直接返回false
    if (count >= capacity)
        return false;
    //队列未满
    //l变量保存此时的last队尾结点,可能为null
    Node<E> l = last;
    //新结点的前驱设置为l
    node.prev = l;
    //last指向新结点
    last = node;
    //如果first也为null,说明队列为空
    if (first == null)
        //那么first也指向该结点
        first = node;
        //否则说明队列不为空,l也肯定不为null
    else
        //l的后继指向新结点
        l.next = node;
    //计数器自增1
    ++count;
    //添加了元素结点之后,唤醒在notEmpty等待的消费线程
    notEmpty.signal();
    //返回true
    return true;
}

2.3.1.2 put(e)方法

public void put(E e)

将指定的元素插入此队列的尾部,如果该队列已满,则线程等待。

如果因为获取不到锁而在同步队列中等待的时候被中断则会继续等待,即不响应中断,如果因为队列满了在条件队列中等待的时候在其他线程调用signal、signalAll方法唤醒该线程之前就因为中断而被唤醒了,也会抛出InterruptedException。另外,如果指定元素为 null则抛出NullPointerException 异常。

内部就是调用的putLast(e)方法。

/**
 * 将指定的元素插入此队列的尾部,如果该队列已满,则线程等待。
 *
 * @throws NullPointerException 如果指定的元素为 null
 * @throws InterruptedException 如果因为获取不到锁而在同步队列中等待的时候被中断则会继续等待,即不响应中断,
 *                              如果因为队列满了在条件队列中等待的时候在其他线程调用signal、signalAll方法唤醒该线程之前就因为中断而被唤醒了,也会抛出InterruptedException。
 */
public void put(E e) throws InterruptedException {
    //内部直接调用putLast方法
    putLast(e);
}

2.3.1.3 offerLast(e)方法

public boolean offerLast(E e)

将指定的元素插入到此队列的尾部。在成功时返回 true,如果此队列已满,则不阻塞,则立即返回 false。

如果指定元素e为null,则抛出NullPointerException异常。

如果因为获取不到锁而在同步队列中等待的时候被中断则会继续等待,即不响应中断。这里的“不会阻塞”是说的获取锁之后如果发现此队列已满,则立即返回 false,而不会阻塞在条件队列上!因此如果该锁被其他线程获取了,当前调用offer方法的线程还是会因为获取不到锁而被阻塞在lock的同步队列中!

相比于putLast方法,内部仅仅会调用一次linkLast方法,无论成功还是失败。

/**
 * 将指定的元素插入到此队列的尾部。
 *
 * @param e 指定元素
 * @return 在成功时返回 true,如果此队列已满,则不阻塞,则立即返回 false。
 * @throws NullPointerException 如果指定的元素为 null
 */
public boolean offerLast(E e) {
    //e的校验
    if (e == null) throw new NullPointerException();
    //新建Node结点
    Node<E> node = new Node<E>(e);
    final ReentrantLock lock = this.lock;
    //不可中断的等待获取锁,即不响应中断
    lock.lock();
    try {
        //仅仅调用一次linkLast方法,返回linkLast的返回值,无论成功还是失败
        return linkLast(node);
    } finally {
        //释放锁
        lock.unlock();
    }
}

2.3.1.4 offer(e)方法

public boolean offer(E e)

将指定的元素插入到此队列的尾部。在成功时返回 true,如果此队列已满,则不阻塞,则立即返回 false。

如果指定元素e为null,则抛出NullPointerException异常。

如果因为获取不到锁而在同步队列中等待的时候被中断则会继续等待,即不响应中断。这里的“不会阻塞”是说的获取锁之后如果发现此队列已满,则立即返回 false,而不会阻塞在条件队列上!因此如果该锁被其他线程获取了,当前调用offer方法的线程还是会因为获取不到锁而被阻塞在lock的同步队列中!

内部就是调用的offerLast(e)方法。

/**
 * 将指定的元素插入到此队列的尾部。
 *
 * @param e 指定元素
 * @return 在成功时返回 true,如果此队列已满,则不阻塞,则立即返回 false。
 * @throws NullPointerException 如果指定的元素为 null
 */
public boolean offer(E e) {
    //内部直接调用offerLast方法
    return offerLast(e);
}

2.3.1.5 offerLast(e, timeout, unit)方法

public boolean offerLast(E e, long timeout, TimeUnit unit)

将指定的元素插入此队列的尾部,如果该队列已满,则在到达指定的等待时间之前等待可用的空间。如果插入成功,则返回 true;如果在空间可用前超过了指定的等待时间,则返回 false。

如果因为获取不到锁而在同步队列中等待的时候被中断则抛出InterruptedException,即响应中断,如果因为队列满了在条件队列中等待的时候在其他线程调用signal、signalAll方法唤醒该线程之前就因为中断而被唤醒了,也会抛出InterruptedException。另外,如果指定元素e为 null则抛出NullPointerException 异常。

相比于putLast,并不是无限循环,而是循环指定的时间。

/**
 * 将指定的元素插入此队列的尾部,如果该队列已满,则在到达指定的等待时间之前等待可用的空间。
 *
 * @return 如果插入成功,则返回 true;如果在空间可用前超过了指定的等待时间,则返回 false。
 * @throws NullPointerException 如果指定元素e为 null
 * @throws InterruptedException 如果因为获取不到锁而在同步队列中等待的时候被中断则抛出InterruptedException,即响应中断
 *                              如果因为队列满了在条件队列中等待的时候在其他线程调用signal、signalAll方法唤醒该线程之前就因为中断而被唤醒了,也会抛出InterruptedException。
 */
public boolean offerLast(E e, long timeout, TimeUnit unit)
        throws InterruptedException {
    //e的校验
    if (e == null) throw new NullPointerException();
    //新建Node结点
    Node<E> node = new Node<E>(e);
    //计算超时时间纳秒
    long nanos = unit.toNanos(timeout);
    final ReentrantLock lock = this.lock;
    //可中断的等待获取锁,即响应中断
    lock.lockInterruptibly();
    try {
        /*
         *循环调用linkLast尝试将node结点加入队尾
         */
        while (!linkLast(node)) {
            //如果加入失败,判断剩余超时时间是否小于等于0,即是否超时
            if (nanos <= 0)
                //如果超时,那么直接返回false
                return false;
            //如果没有超时,该线程在notFull条件队列中等待nanos时间
            //被唤醒或者中断之后,将会返回剩余的等待时间,随后继续循环
            nanos = notFull.awaitNanos(nanos);
        }
        //如果加入成功,那么返回true
        return true;
    } finally {
        //释放锁
        lock.unlock();
    }
}

2.3.1.6 offer(e, timeout, unit)方法

public boolean offer(E e, long timeout, TimeUnit unit)

将指定的元素插入此队列的尾部,如果该队列已满,则在到达指定的等待时间之前等待可用的空间。如果插入成功,则返回 true;如果在空间可用前超过了指定的等待时间,则返回 false。

如果因为获取不到锁而在同步队列中等待的时候被中断则抛出InterruptedException,即响应中断,如果因为队列满了在条件队列中等待的时候在其他线程调用signal、signalAll方法唤醒该线程之前就因为中断而被唤醒了,也会抛出InterruptedException。另外,如果指定元素e为 null则抛出NullPointerException 异常。

内部就是调用的offerLast(e, timeout, unit)方法。

/**
 * 将指定的元素插入此队列的尾部,如果该队列已满,则在到达指定的等待时间之前等待可用的空间。
 *
 * @return 如果插入成功,则返回 true;如果在空间可用前超过了指定的等待时间,则返回 false。
 * @throws NullPointerException 如果指定元素e为 null
 * @throws InterruptedException 如果因为获取不到锁而在同步队列中等待的时候被中断则抛出InterruptedException,即响应中断
 *                              如果因为队列满了在条件队列中等待的时候在其他线程调用signal、signalAll方法唤醒该线程之前就因为中断而被唤醒了,也会抛出InterruptedException。
 */
public boolean offer(E e, long timeout, TimeUnit unit)
        throws InterruptedException {
    //内部直接调用offerLast(e, timeout, unit)方法
    return offerLast(e, timeout, unit);
}

2.3.1.7 addLast(e)方法

public void addLast(E e)

将指定元素插入此队列尾部。成功时返回true,如果当前没有可用的空间,则抛出 IllegalStateException,如果e元素为null则抛出NullPointerException 异常。当使用有容量限制的队列时,通常首选 offerLast方法。

如果因为获取不到锁而在同步队列中等待的时候被中断也会继续等待获取锁,即不响应中断。

内部实际上就是调用的offerLast方法,并根据offerLast方法的返回值判断是否需要抛出异常!

/**
 * 将指定元素插入此队列中。成功时返回 true,如果当前没有可用的空间,则抛出 IllegalStateException。
 * 当使用有容量限制的队列时,通常首选 offerLast。
 *
 * @param e 指定元素
 * @throws IllegalStateException 如果当前没有可用的空间
 * @throws NullPointerException  如果e元素为null
 */
public void addLast(E e) {
    //实际上调用的offerLast方法
    if (!offerLast(e))
        //如果插入失败直接抛出IllegalStateException异常
        throw new IllegalStateException("Deque full");
}

2.3.1.8 add(e)方法

public boolean add(E e)

将指定元素插入此队列尾部。成功时返回 true,如果当前没有可用的空间,则抛出 IllegalStateException,如果e元素为null则抛出NullPointerException 异常。当使用有容量限制的队列时,通常首选 offer方法。

如果因为获取不到锁而在同步队列中等待的时候被中断也会继续等待获取锁,即不响应中断。如果e元素为null则抛出NullPointerException 异常。

内部实际上就是调用的addLast方法!

/**
 * 将指定的元素插入到此队列中(如果立即可行且不会违反容量限制),在成功时返回 true,如果当前没有可用空间,则抛出 IllegalStateException。
 *
 * @param e 指定元素
 * @return 成功时返回 true
 * @throws IllegalStateException    如果此时由于容量限制不能添加元素
 * @throws ClassCastException       如果指定元素的类不允许将该元素添加到此队列中
 * @throws NullPointerException     如果指定元素为 null 并且此队列不允许 null 元素
 * @throws IllegalArgumentException 如果此元素的某些属性不允许将该元素添加到此队列中
 */
public boolean add(E e) {
    //内部直接调用addLast方法
    addLast(e);
    //如果addLast没有抛出异常,那么返回true
    return true;
}

2.3.2 入队头

2.3.2.1 putFirst(e)方法

public void putFirst(E e)

将指定的元素插入此双端队列的头部,如果该队列已满,则线程等待。

如果因为获取不到锁而在同步队列中等待的时候被中断则会继续等待,即不响应中断,如果因为队列满了在条件队列中等待的时候在其他线程调用signal、signalAll方法唤醒该线程之前就因为中断而被唤醒了,也会抛出InterruptedException。另外,如果指定元素为 null则抛出NullPointerException 异常。

很简单,大概步骤为:

  1. 指定元素e的null检测,通过之后新建Node结点传入指定元素e。
  2. 不可中断的等待获取锁,即不响应中断,获取不到就在该锁的同步队列中等待被唤醒,等待时被中断之后会继续尝试获取锁;
  3. 获取到锁之后,循环调用linkFirst尝试将node结点加入队头。如果linkFirst返回false,表示队列满了加入失败,那么该线程在notFull条件队列中等待并释放锁,被唤醒之后会继续尝试获取锁、并继续循环调用linkFirst。
  4. 如果linkFirst返回true,表示加入成功,那么循环结束。
  5. 无论过程中发生了什么,最后的finally中解锁。
/**
 * 将指定的元素插入此双端队列的头部,如果该队列已满,则线程等待。
 *
 * @throws NullPointerException 如果指定的元素为 null
 * @throws InterruptedException 如果因为获取不到锁而在同步队列中等待的时候被中断则会继续等待,即不响应中断,
 *                              如果因为队列满了在条件队列中等待的时候在其他线程调用signal、signalAll方法唤醒该线程之前就因为中断而被唤醒了,也会抛出InterruptedException。
 */
public void putFirst(E e) throws InterruptedException {
    //e的校验
    if (e == null) throw new NullPointerException();
    //新建Node结点
    Node<E> node = new Node<E>(e);
    final ReentrantLock lock = this.lock;
    //不可中断的等待获取锁,即不响应中断
    lock.lock();
    try {
        /*
         *循环调用linkFirst尝试将node结点加入队头
         */
        while (!linkFirst(node))
            //如果失败,表示队列满了,那么该线程在notFull条件队列中等待并释放锁,被唤醒之后会继续尝试获取锁、并循环判断
            notFull.await();
    } finally {
        //释放锁
        lock.unlock();
    }
}
2.3.2.1.1 linkFirst链接尾结点

linkFirst用于将指定node结点链接到队列头部成为新的头结点,原理很简单就是在原头结点first指向的结点前面新添加一个node结点,同时建立prev和next的引用关系。如果最开始队列为空,那么head和last都指向该node结点。

如果队列满了,那么直接返回false,如果链接成功,那么将会唤醒一个在notEmpty等待的消费线程,并返回true。

/**
 * 将指定结点链接到队列头部成为新的头结点
 */
private boolean linkFirst(Node<E> node) {
    // assert lock.isHeldByCurrentThread();
    //如果队列满了,那么直接返回false
    if (count >= capacity)
        return false;
    //队列未满
    //f变量保存此时的first队头结点,可能为null
    Node<E> f = first;
    //新结点的前驱设置为f
    node.next = f;
    //first指向新结点
    first = node;
    //如果last也为null,说明队列为空
    if (last == null)
        //那么last也指向该结点
        last = node;
        //否则说明队列不为空,f也肯定不为null
    else
        //f的前驱指向新结点
        f.prev = node;
    //计数器自增1
    ++count;
    //添加了元素结点之后,唤醒在notEmpty等待的消费线程
    notEmpty.signal();
    //返回true
    return true;
}

2.3.2.2 offerFirst(e)方法

public boolean offerFirst(E e)

将指定的元素插入到此队列的头部。在成功时返回 true,如果此队列已满,则不阻塞,则立即返回 false。

如果指定元素e为null,则抛出NullPointerException异常。

如果因为获取不到锁而在同步队列中等待的时候被中断则会继续等待,即不响应中断。这里的“不会阻塞”是说的获取锁之后如果发现此队列已满,则立即返回 false,而不会阻塞在条件队列上!因此如果该锁被其他线程获取了,当前调用offer方法的线程还是会因为获取不到锁而被阻塞在lock的同步队列中!

相比于putFirst方法,内部仅仅会调用一次linkFirst方法,无论成功还是失败。

/**
 * 将指定的元素插入到此队列的头部。
 *
 * @param e 指定元素
 * @return 在成功时返回 true,如果此队列已满,则不阻塞,则立即返回 false。
 * @throws NullPointerException 如果指定的元素为 null
 */
public boolean offerFirst(E e) {
    //e的校验
    if (e == null) throw new NullPointerException();
    //新建Node结点
    Node<E> node = new Node<E>(e);
    final ReentrantLock lock = this.lock;
    //不可中断的等待获取锁,即不响应中断
    lock.lock();
    try {
        //仅仅调用一次linkFirst方法,返回linkFirst的返回值,无论成功还是失败
        return linkFirst(node);
    } finally {
        //释放锁
        lock.unlock();
    }
}

2.3.2.3 offerFirst(e, timeout, unit)方法

public boolean offerFirst(E e,long timeout,TimeUnit unit)

将指定的元素插入此队列的头部,如果该队列已满,则在到达指定的等待时间之前等待可用的空间。如果插入成功,则返回 true;如果在空间可用前超过了指定的等待时间,则返回 false。

如果因为获取不到锁而在同步队列中等待的时候被中断则抛出InterruptedException,即响应中断,如果因为队列满了在条件队列中等待的时候在其他线程调用signal、signalAll方法唤醒该线程之前就因为中断而被唤醒了,也会抛出InterruptedException。另外,如果指定元素e为 null则抛出NullPointerException 异常。

相比于putFirst,并不是无限循环,而是循环指定的时间。

/**
 * 将指定的元素插入此队列的头部,如果该队列已满,则在到达指定的等待时间之前等待可用的空间。
 *
 * @return 如果插入成功,则返回 true;如果在空间可用前超过了指定的等待时间,则返回 false。
 * @throws NullPointerException 如果指定元素e为 null
 * @throws InterruptedException 如果因为获取不到锁而在同步队列中等待的时候被中断则抛出InterruptedException,即响应中断
 *                              如果因为队列满了在条件队列中等待的时候在其他线程调用signal、signalAll方法唤醒该线程之前就因为中断而被唤醒了,也会抛出InterruptedException。
 */
public boolean offerFirst(E e, long timeout, TimeUnit unit)
        throws InterruptedException {
    //e的校验
    if (e == null) throw new NullPointerException();
    //新建Node结点
    Node<E> node = new Node<E>(e);
    //计算超时时间纳秒
    long nanos = unit.toNanos(timeout);
    final ReentrantLock lock = this.lock;
    //可中断的等待获取锁,即响应中断
    lock.lockInterruptibly();
    try {
        /*
         *循环调用linkFirst尝试将node结点加入队头
         */
        while (!linkFirst(node)) {
            //如果加入失败,判断剩余超时时间是否小于等于0,即是否超时
            if (nanos <= 0)
                //如果超时,那么直接返回false
                return false;
            //如果没有超时,该线程在notFull条件队列中等待nanos时间
            //被唤醒或者中断之后,将会返回剩余的等待时间,随后继续循环
            nanos = notFull.awaitNanos(nanos);
        }
        //如果加入成功,那么返回true
        return true;
    } finally {
        //释放锁
        lock.unlock();
    }
}

2.3.2.4 addFirst(e)方法

public void addFirst(E e)

将指定元素插入此队列头部。成功时返回true,如果当前没有可用的空间,则抛出 IllegalStateException,如果e元素为null则抛出NullPointerException 异常。当使用有容量限制的队列时,通常首选 offerFirst方法。

如果因为获取不到锁而在同步队列中等待的时候被中断也会继续等待获取锁,即不响应中断。如果e元素为null则抛出NullPointerException 异常。

内部实际上就是调用的offerFirst方法,并根据offerFirst方法的返回值判断是否需要抛出异常!

/**
 * 将指定元素插入此队列头部。成功时返回 true,如果当前没有可用的空间,则抛出 IllegalStateException。
 * 当使用有容量限制的队列时,通常首选 offerFirst。
 *
 * @param e 指定元素
 * @throws IllegalStateException 如果当前没有可用的空间
 * @throws NullPointerException  如果e元素为null
 */
public void addFirst(E e) {
    //实际上调用的offerFirst方法
    if (!offerFirst(e))
        //如果插入失败直接抛出IllegalStateException异常
        throw new IllegalStateException("Deque full");
}

2.4 出队操作

2.4.1 出队头

2.4.1.1 takeFirst()方法

public E takeFirst()

获取并移除此双端队列的头部元素,如果该队列已空,则线程等待。

如果因为获取不到锁而在同步队列中等待的时候被中断则会继续等待,即不响应中断,如果因为队列满了在条件队列中等待的时候在其他线程调用signal、signalAll方法唤醒该线程之前就因为中断而被唤醒了,也会抛出InterruptedException。

很简单,大概步骤为:

  1. 不可中断的等待获取锁,即不响应中断,获取不到就在该锁的同步队列中等待被唤醒,等待时被中断之后会继续尝试获取锁;
  2. 获取到锁之后,循环调用unlinkFirst尝试移除队头。如果unlinkFirst返回null,表示队列空了加移除失败,那么该线程在notEmpty条件队列中等待并释放锁,被唤醒之后会继续尝试获取锁、并继续循环调用unlinkFirst。
  3. 如果unlinkFirst返回值x不为null,表示加入成功,那么循环结束,返回x。
  4. 无论过程中发生了什么,最后的finally中解锁。
/**
 * 获取并移除此双端队列的头部元素,如果该队列已空,则线程等待。
 *
 * @return 被移除的队头
 * @throws InterruptedException 如果因为获取不到锁而在同步队列中等待的时候被中断则会继续等待,即不响应中断,
 *                              如果因为队列满了在条件队列中等待的时候在其他线程调用signal、signalAll方法唤醒该线程之前就因为中断而被唤醒了,也会抛出InterruptedException。
 */
public E takeFirst() throws InterruptedException {
    final ReentrantLock lock = this.lock;
    //不可中断的等待获取锁,即不响应中断
    lock.lock();
    try {
        E x;
        /*
         *循环调用unlinkFirst尝试将头结点出队并返回结点的item值x
         */
        while ((x = unlinkFirst()) == null)
            //如果x为null,表示队列空了,那么该线程在notEmpty条件队列中等待并释放锁,被唤醒之后会继续尝试获取锁、并循环判断
            notEmpty.await();
        //x不为null,表示出队成功,结束循环,返回x
        return x;
    } finally {
        //释放锁
        lock.unlock();
    }
}
2.4.1.1.1 unlinkFirst移除队头

unlinkFirst用于移除链表头结点并将其后继作为新的头结点,原理很简单就是移除原头结点first和其后继的prev和next的引用关系。如果移除之后队列为空,那么head和last都指向null。

如果队列空了,那么直接返回null,如果移除成功,那么将会唤醒一个在notFull等待的生产线程,并返回头结点的item值。

另外,这里被移除的头结点会将next的引用指向自己,除了能够正常的被GC回收之外,同时用于迭代器辨认是该结点被删除了而不是到达了队列末尾,因为迭代器中以后继为null表示迭代完毕,在迭代器的succ方法部分会讲到!

/**
 * 尝试将头结点出队并返回结点的item值x
 *
 * @return 不为null 出队成功;null 队列已空
 */
private E unlinkFirst() {
    // assert lock.isHeldByCurrentThread();
    //f变量保存此时的first队头结点,可能为null
    Node<E> f = first;
    //如果f为null,表示队列为空,直接返回null
    if (f == null)
        return null;
    //获取f的后继结点n
    Node<E> n = f.next;
    //获取f结点的item值item
    E item = f.item;
    //f的item置空
    f.item = null;
    //f的后继指向自己,结点出队列,同时用于迭代器辨认是该结点被删除了而不是到达了队列末尾,因为迭代器中以后继为null表示迭代完毕,在迭代器的succ方法部分会讲到
    f.next = f; // help GC
    //first指向f的后继n
    first = n;
    /*如果n为null*/
    if (n == null)
        //那么last指向null
        last = null;
        /*如果n不为null*/
    else
        //n的前驱置空
        n.prev = null;
    //计数器自减1
    --count;
    //出队成功之后,唤醒在notFull等待的生产线程
    notFull.signal();
    //返回item
    return item;
}

2.4.1.2 take()方法

获取并移除此双端队列的头部元素,如果该队列已空,则线程等待。

如果因为获取不到锁而在同步队列中等待的时候被中断则会继续等待,即不响应中断,如果因为队列满了在条件队列中等待的时候在其他线程调用signal、signalAll方法唤醒该线程之前就因为中断而被唤醒了,也会抛出InterruptedException。

内部就是调用的takeFirst()方法。

/**
 * 获取并移除此双端队列的头部元素,如果该队列已空,则线程等待。
 *
 * @return 被移除的队头
 * @throws InterruptedException 如果因为获取不到锁而在同步队列中等待的时候被中断则会继续等待,即不响应中断,
 *                              如果因为队列满了在条件队列中等待的时候在其他线程调用signal、signalAll方法唤醒该线程之前就因为中断而被唤醒了,也会抛出InterruptedException。
 */
public E take() throws InterruptedException {
    //内部直接调用takeFirst方法
    return takeFirst();
}

2.4.1.3 pollFirst()方法

public E pollFirst()

获取并移除此双端队列的头部元素,如果该队列已空,则返回null。

如果因为获取不到锁而在同步队列中等待的时候被中断则会继续等待,即不响应中断。这里的“不会阻塞”是说的获取锁之后如果发现此队列已空,则立即返回 null,而不会阻塞在条件队列上!因此如果该锁被其他线程获取了,当前调用offer方法的线程还是会因为获取不到锁而被阻塞在lock的同步队列中!

相比于takeFirst方法,内部仅仅会调用一次unlinkFirst方法,无论返回什么。

/**
 * @return 获取并移除此双端队列的头部元素,如果该队列已空,则返回null。
 */
public E pollFirst() {
    final ReentrantLock lock = this.lock;
    //不可中断的等待获取锁,即不响应中断
    lock.lock();
    try {
        //仅仅调用一次unlinkFirst方法,返回unlinkFirst的返回值,无论成功还是失败
        return unlinkFirst();
    } finally {
        //释放锁
        lock.unlock();
    }
}

2.4.1.4 poll()方法

public E poll()

获取并移除此双端队列的头部元素,如果该队列已空,则返回null。

如果因为获取不到锁而在同步队列中等待的时候被中断则会继续等待,即不响应中断。这里的“不会阻塞”是说的获取锁之后如果发现此队列已空,则立即返回 null,而不会阻塞在条件队列上!因此如果该锁被其他线程获取了,当前调用offer方法的线程还是会因为获取不到锁而被阻塞在lock的同步队列中!

内部就是调用的pollFirst()方法。

/**
 * @return 获取并移除此双端队列的头部元素,如果该队列已空,则返回null。
 */
public E poll() {
    //内部就是调用的pollFirst()方法。
    return pollFirst();
}

2.4.1.5 pollFirst(timeout, unit)方法

public E pollFirst(long timeout,TimeUnit unit)

获取并移除此双端队列的头部元素,如果该队列已空,则在到达指定的等待时间之前等待队列非空。如果移除成功,则返回被移除的头部元素;如果在队列非空前超过了指定的等待时间,则返回 null。

如果因为获取不到锁而在同步队列中等待的时候被中断则抛出InterruptedException,即响应中断,如果因为队列满了在条件队列中等待的时候在其他线程调用signal、signalAll方法唤醒该线程之前就因为中断而被唤醒了,也会抛出InterruptedException。

相比于takeFirst方法,并不是无限循环,而是循环指定的时间。

/**
 * 获取并移除此双端队列的头部元素,如果该队列已空,则在到达指定的等待时间之前等待队列非空。
 *
 * @return 如果移除成功,则返回被移除的头部元素;如果在队列非空前超过了指定的等待时间,则返回 null。
 * @throws InterruptedException 如果因为获取不到锁而在同步队列中等待的时候被中断则抛出InterruptedException,即响应中断,
 *                              如果因为队列满了在条件队列中等待的时候在其他线程调用signal、signalAll方法唤醒该线程之前就因为中断而被唤醒了,也会抛出InterruptedException。
 */
public E pollFirst(long timeout, TimeUnit unit)
        throws InterruptedException {
    //计算超时时间纳秒
    long nanos = unit.toNanos(timeout);
    final ReentrantLock lock = this.lock;
    //可中断的等待获取锁,即响应中断
    lock.lockInterruptibly();
    try {
        E x;
        /*
         *循环调用unlinkFirst尝试将头结点出队并返回结点的item值x
         */
        while ((x = unlinkFirst()) == null) {
            //如果出队失败,判断剩余超时时间是否小于等于0,即是否超时
            if (nanos <= 0)
                //如果超时,那么直接返回null
                return null;
            //如果没有超时,该线程在notEmpty条件队列中等待nanos时间
            //被唤醒或者中断之后,将会返回剩余的等待时间,随后继续循环
            nanos = notEmpty.awaitNanos(nanos);
        }
        //如果出队成功,那么返回true
        return x;
    } finally {
        //释放锁
        lock.unlock();
    }
}

2.4.1.6 poll(timeout, unit)方法

public E poll(long timeout,TimeUnit unit)

获取并移除此双端队列的头部元素,如果该队列已空,则在到达指定的等待时间之前等待队列非空。如果移除成功,则返回被移除的头部元素;如果在队列非空前超过了指定的等待时间,则返回 null。

如果因为获取不到锁而在同步队列中等待的时候被中断则抛出InterruptedException,即响应中断,如果因为队列满了在条件队列中等待的时候在其他线程调用signal、signalAll方法唤醒该线程之前就因为中断而被唤醒了,也会抛出InterruptedException。

内部就是调用的pollFirst(timeout, unit)方法。

/**
 * 获取并移除此双端队列的头部元素,如果该队列已空,则在到达指定的等待时间之前等待队列非空。
 *
 * @return 如果移除成功,则返回被移除的头部元素;如果在队列非空前超过了指定的等待时间,则返回 null。
 * @throws InterruptedException 如果因为获取不到锁而在同步队列中等待的时候被中断则抛出InterruptedException,即响应中断,
 *                              如果因为队列满了在条件队列中等待的时候在其他线程调用signal、signalAll方法唤醒该线程之前就因为中断而被唤醒了,也会抛出InterruptedException。
 */
public E poll(long timeout, TimeUnit unit) throws InterruptedException {
    //内部就是调用的pollFirst(timeout, unit)方法。
    return pollFirst(timeout, unit);
}

2.4.1.7 removeFirst()方法

public E removeFirst()

获取并移除此双端队列的头部元素。此方法与 pollFirst 唯一的不同在于如果此双端队列为空,它将抛出一个NoSuchElementException异常。

内部实际上就是调用的pollFirst方法,并根据pollFirst方法的返回值判断是否需要抛出异常!

/**
 * @return 获取并移除此双端队列的头部元素。
 * @throws NoSuchElementException 于如果此双端队列为空
 */
public E removeFirst() {
    //实际上调用的pollFirst方法
    E x = pollFirst();
    //如果返回值为null,那么抛出NoSuchElementException
    if (x == null) throw new NoSuchElementException();
    //返回x
    return x;
}

2.4.1.8 remove()方法

public E remove()

获取并移除此双端队列的头部元素。此方法与 pollFirst 唯一的不同在于如果此双端队列为空,它将抛出一个NoSuchElementException异常。

内部实际上就是调用的removeFirst方法。

/**
 * @return 获取并移除此双端队列的头部元素。
 * @throws NoSuchElementException 如果此双端队列为空
 */
public E remove() {
    //内部实际上就是调用的removeFirst方法。
    return removeFirst();
}

2.4.2 出队尾

2.4.2.1 takeLast()方法

public E takeLast()

获取并移除此双端队列的尾部元素,如果该队列已空,则线程等待。

如果因为获取不到锁而在同步队列中等待的时候被中断则会继续等待,即不响应中断,如果因为队列满了在条件队列中等待的时候在其他线程调用signal、signalAll方法唤醒该线程之前就因为中断而被唤醒了,也会抛出InterruptedException。

很简单,大概步骤为:

  1. 不可中断的等待获取锁,即不响应中断,获取不到就在该锁的同步队列中等待被唤醒,等待时被中断之后会继续尝试获取锁;
  2. 获取到锁之后,循环调用unlinkLast尝试移除队尾。如果unlinkLast返回null,表示队列空了移除失败,那么该线程在notEmpty条件队列中等待并释放锁,被唤醒之后会继续尝试获取锁、并继续循环调用unlinkLast。
  3. 如果unlinkLast返回值x不为null,表示加入成功,那么循环结束,返回x。
  4. 无论过程中发生了什么,最后的finally中解锁。
/**
 * 获取并移除此双端队列的尾部元素,如果该队列已空,则线程等待。
 *
 * @return 被移除的队尾item
 * @throws InterruptedException 如果因为获取不到锁而在同步队列中等待的时候被中断则会继续等待,即不响应中断,
 *                              如果因为队列满了在条件队列中等待的时候在其他线程调用signal、signalAll方法唤醒该线程之前就因为中断而被唤醒了,也会抛出InterruptedException。
 */
public E takeLast() throws InterruptedException {
    final ReentrantLock lock = this.lock;
    //不可中断的等待获取锁,即不响应中断
    lock.lock();
    try {
        E x;
        /*
         *循环调用unlinkLast尝试将尾结点出队并返回结点的item值x
         */
        while ((x = unlinkLast()) == null)
            //如果x为null,表示队列空了,那么该线程在notEmpty条件队列中等待并释放锁,被唤醒之后会继续尝试获取锁、并循环判断
            notEmpty.await();
        //x不为null,表示出队成功,结束循环,返回x
        return x;
    } finally {
        //释放锁
        lock.unlock();
    }
}

2.4.2.2 pollLast()方法

public E pollLast()

获取并移除此双端队列的尾部元素,如果该队列已空,则返回null。

如果因为获取不到锁而在同步队列中等待的时候被中断则会继续等待,即不响应中断。这里的“不会阻塞”是说的获取锁之后如果发现此队列已空,则立即返回 null,而不会阻塞在条件队列上!因此如果该锁被其他线程获取了,当前调用offer方法的线程还是会因为获取不到锁而被阻塞在lock的同步队列中!

相比于takeLast方法,内部仅仅会调用一次unlinkLast方法,无论返回什么。

/**
 * @return 获取并移除此双端队列的尾部元素,如果该队列已空,则返回null。
 */
public E pollLast() {
    final ReentrantLock lock = this.lock;
    //不可中断的等待获取锁,即不响应中断
    lock.lock();
    try {
        //仅仅调用一次unlinkLast方法,返回unlinkLast的返回值,无论成功还是失败
        return unlinkLast();
    } finally {
        //释放锁
        lock.unlock();
    }
}

2.4.2.3 pollLast(timeout, unit)方法

public E pollLast(long timeout, TimeUnit unit)

获取并移除此双端队列的尾部元素,如果该队列已空,则在到达指定的等待时间之前等待队列非空。如果移除成功,则返回被移除的尾部元素;如果在队列非空前超过了指定的等待时间,则返回 null。

如果因为获取不到锁而在同步队列中等待的时候被中断则抛出InterruptedException,即响应中断,如果因为队列满了在条件队列中等待的时候在其他线程调用signal、signalAll方法唤醒该线程之前就因为中断而被唤醒了,也会抛出InterruptedException。

相比于takeLast方法,并不是无限循环,而是循环指定的时间。

/**
 * 获取并移除此双端队列的尾部元素,如果该队列已空,则在到达指定的等待时间之前等待队列非空。
 *
 * @param timeout 超时时间
 * @param unit    时间单位
 * @return 如果移除成功,则返回被移除的尾部元素;如果在队列非空前超过了指定的等待时间,则返回 null。
 * @throws InterruptedException 如果因为获取不到锁而在同步队列中等待的时候被中断则抛出InterruptedException,即响应中断
 *                              如果因为队列满了在条件队列中等待的时候在其他线程调用signal、signalAll方法唤醒该线程之前就因为中断而被唤醒了,也会抛出InterruptedException。
 */
public E pollLast(long timeout, TimeUnit unit)
        throws InterruptedException {
    //计算超时时间纳秒
    long nanos = unit.toNanos(timeout);
    final ReentrantLock lock = this.lock;
    //可中断的等待获取锁,即响应中断
    lock.lockInterruptibly();
    try {
        E x;
        /*
         *循环调用unlinkLast尝试将尾结点出队并返回结点的item值x
         */
        while ((x = unlinkLast()) == null) {
            //如果出队失败,判断剩余超时时间是否小于等于0,即是否超时
            if (nanos <= 0)
                //如果超时,那么直接返回null
                return null;
            //如果没有超时,该线程在notEmpty条件队列中等待nanos时间
            //被唤醒或者中断之后,将会返回剩余的等待时间,随后继续循环
            nanos = notEmpty.awaitNanos(nanos);
        }
        //如果出队成功,那么返回true
        return x;
    } finally {
        //释放锁
        lock.unlock();
    }
}

2.4.2.4 removeLast()方法

public E removeLast()

获取并移除此双端队列的尾部元素。此方法与 pollLast唯一的不同在于如果此双端队列为空,它将抛出一个NoSuchElementException异常。

内部实际上就是调用的pollLast方法,并根据pollLast方法的返回值判断是否需要抛出异常!

/**
 * @return 获取并移除此双端队列的尾部元素。
 * @throws NoSuchElementException 于如果此双端队列为空
 */
public E removeLast() {
    //实际上调用的pollFirst方法
    E x = pollLast();
    //如果返回值为null,那么抛出NoSuchElementException
    if (x == null) throw new NoSuchElementException();
    //返回x
    return x;
}

2.4.3 remove(o)方法

public boolean remove(Object o)

从此队列中移除指定元素的单个实例(如果存在)。如果移除成功则返回 true;没有找到指定元素或者指定元素为null则返回false。

从队列头开始遍历队列,查找和具有和指定元素o使用equals比较返回true的item值的元素p,然后调用unlink 移除p结点!

/**
 * 从此队列中移除指定元素的单个实例(如果存在)。
 *
 * @param o 指定元素
 * @return 如果移除成功则返回 true;没有找到指定元素或者指定元素为null则返回false。
 */
public boolean remove(Object o) {
    //内部调用removeFirstOccurrence方法
    return removeFirstOccurrence(o);
}

/**
 * 移除第一次出现的指定元素
 *
 * @param o 指定元素
 * @return 如果移除成功则返回 true;没有找到指定元素或者指定元素为null则返回false。
 */
public boolean removeFirstOccurrence(Object o) {
    //如果o为null,直接返回null
    if (o == null) return false;
    final ReentrantLock lock = this.lock;
    //不可中断的等待获取锁,即不响应中断
    lock.lock();
    try {
        /*
         * 重头到尾遍历整个链表,查找与具有指定元素o相等的item值的结点
         */
        for (Node<E> p = first; p != null; p = p.next) {
            //使用equals比较
            if (o.equals(p.item)) {
                //将该结点移除队列
                unlink(p);
                //返回true
                return true;
            }
        }
        //没找到,返回false
        return false;
    } finally {
        //释放锁
        lock.unlock();
    }
}

2.4.3.1 unlink移除指定结点

unlink方法用于移除指定的结点。

这里被移除的结点如果是中间结点,会将item置为null,并且它的前驱后继直接关联,但是它自己的前驱后继关系并没有移除,除了表示该结点出队列之外,同时用于迭代器辨认是该中间结点是否被删除了,因为可能存在迭代器正在迭代这个中间结点,此时迭代器就可以跳过这个结点,在迭代器的succ方法部分会讲到。

/**
 * 移除指定结点x
 */
void unlink(Node<E> x) {
    // assert lock.isHeldByCurrentThread();
    //获取x的前驱p
    Node<E> p = x.prev;
    //获取x的后继n
    Node<E> n = x.next;
    /*如果p为null,那么相当于移除头结点,直接调用unlinkFirst方法即可*/
    if (p == null) {
        unlinkFirst();
    }
    /*否则,如果n为null,那么相当于移除尾结点,直接调用unlinkLast方法即可*/
    else if (n == null) {
        unlinkLast();
    }
    /*否则,表示移除中间结点*/
    else {
        //p的后继设置为n
        p.next = n;
        //n的前驱设置为p
        n.prev = p;
        //x结点的item值置为null
        x.item = null;
        // Don't mess with x's links.  They may still be in use by
        // an iterator.
        //这里没有将x的prev和next引用置空,因为可能存在迭代器正在迭代这个结点,在迭代器的succ方法部分会讲到。
        //计数器之间一
        --count;
        //出队成功之后,唤醒在notFull等待的生产线程
        notFull.signal();
    }
}

2.5 检查操作

2.5.1 检查队头

2.5.1.1 peekFirst()方法

public E peekFirst()

获取但不移除此队列的头;如果此队列为空,则返回 null。

/**
 * @return 获取但不移除此队列的头;如果此队列为空,则返回 null。
 */
public E peekFirst() {
    final ReentrantLock lock = this.lock;
    //不可中断的等待获取消费者锁,即不响应中断
    lock.lock();
    try {
        //如果first为null,那么返回null,否则返回first的item值
        return (first == null) ? null : first.item;
    } finally {
        //释放锁
        lock.unlock();
    }
}

2.5.1.2 peek()方法

public E peek()

获取但不移除此队列的头;如果此队列为空,则返回 null。

内部直接调用peekFirst方法。

/**
 * @return 获取但不移除此队列的头;如果此队列为空,则返回 null。
 */
public E peek() {
    //内部直接调用peekFirst方法
    return peekFirst();
}

2.5.1.3 getFirst()方法

public E getFirst()

获取但不移除此队列的头;此方法与 peekFirst 唯一的不同在于:如果此双端队列为空,它将抛出一个NoSuchElementException异常。

内部实际上就是调用的peekFirst方法,并根据peekFirst方法的返回值判断是否需要抛出异常!

/**
 * @return 此队列的头
 * @throws NoSuchElementException 如果此队列为空
 */
public E getFirst() {
    //内部调用peekFirst方法获取返回值x
    E x = peekFirst();
    //如果x不为null,那么返回x;否则抛出NoSuchElementException异常
    if (x == null) throw new NoSuchElementException();
    return x;
}

2.5.1.4 element()方法

public E element()

获取但不移除此队列的头;此方法与 peek 的不同之处在于:如果此双端队列为空,它将抛出一个NoSuchElementException异常。

内部实际上就是调用的getFirst方法!

/**
 * 获取但不移除此队列的头;此方法与 peek 的不同之处在于:如果此双端队列为空,它将抛出一个NoSuchElementException异常。
 *
 * @return 此队列的头
 * @throws NoSuchElementException 如果此双端队列为空
 */
public E element() {
    //内部实际上就是调用getFirst方法
    return getFirst();
}

2.5.2 检查队尾

2.5.2.1 peekLast()方法

public E peekLast()

获取但不移除此队列的尾;如果此队列为空,则返回 null。

/**
 * @return 获取但不移除此队列的尾;如果此队列为空,则返回 null。
 */
public E peekLast() {
    final ReentrantLock lock = this.lock;
    //不可中断的等待获取消费者锁,即不响应中断
    lock.lock();
    try {
        //如果last为null,那么返回null,否则返回last的item值
        return (last == null) ? null : last.item;
    } finally {
        //释放锁
        lock.unlock();
    }
}

2.5.2.2 getLast()方法

public E getLast()

获取但不移除此队列的尾;此方法与 peekLast唯一的不同在于:如果此双端队列为空,它将抛出一个NoSuchElementException异常。

内部实际上就是调用的peekLast方法,并根据peekLast方法的返回值判断是否需要抛出异常!

/**
 * @return 此队列的尾
 * @throws NoSuchElementException 如果此双端队列为空
 */
public E getLast() {
    //内部调用peekLast方法获取返回值x
    E x = peekLast();
    //如果x不为null,那么返回x;否则抛出NoSuchElementException异常
    if (x == null) throw new NoSuchElementException();
    return x;
}

2.6 size操作

public int size()

返回此队列中元素的数量。这个方法加了锁,因此返回的是精确值!

public boolean isEmpty()

如果此队列不包含元素,则返回 true。

/**
 * @return 返回此队列中元素的数量
 */
public int size() {
    final ReentrantLock lock = this.lock;
    //不可中断的等待获取lock锁,即不响应中断
    lock.lock();
    try {
        //返回count
        return count;
    } finally {
        //释放锁
        lock.unlock();
    }
}

/**
 * 直接判断size()方法是否返回0
 */
public boolean isEmpty() {
    return size() == 0;
}

2.7 迭代操作

public Iterator iterator()

返回在此双端队列元素上以恰当顺序进行迭代的迭代器。元素将按从第一个(头部)到最后一个(尾部)的顺序返回。和其他并发容器一样,返回的 Iterator 是一个“弱一致”的,不会抛出 ConcurrentModificationException,即支持并发修改,但是不保证迭代获取的元素就是此时队列中的元素!

下面的源码解析包括了迭代器的实现和方法,还是很简单的。弱一致性的原理在注释中也讲的很清楚,实际上下一次要返回的值在上一次迭代的时候就确定了,使用next和nextItem保存,相当于值的快照,弱一致性是很显而易见的!

另外在迭代器的内部succ方法中循环获取下一次要迭代的有效结点的时候,就会判断我们前面说的两种移除结点的情况:

  1. n.next == n,即说明n结点是此前被删除的头结点,这就是takeFirst()、pollFirst()等出队头方法造成的结果,那么此时应该寻找真正的head作为下一个有效结点,因此下一次要迭代的结点就是head;
  2. 否则,n.item == null,即n作为被删除中间结点,这就是remove(o)方法造成的结果,此时n.next和n.prev没有变化,那么n=n.next,跳过这个被删除的中间结点,继续下一次循环向后查找有效的结点。

最后,初始化迭代器、next、remove等方法调用时都需要加锁,因此会影响并发效率!

/**
 * @return 返回在队列中的元素上按适当顺序进行迭代的迭代器Iterator。
 * 和其他并发容器一样,返回的 Iterator 是一个“弱一致”的,不会抛出 ConcurrentModificationException,即支持并发修改,
 * 但是不保证迭代获取的元素就是此时队列中的元素!
 */
public Iterator<E> iterator() {
    //返回一个Itr对象
    return new Itr();
}

/**
 * 位于LinkedBlockingDeque中的迭代器的父类内部类
 */
private abstract class AbstractItr implements Iterator<E> {
    /**
     * 下一个要迭代的结点
     */
    Node<E> next;

    /**
     * 下一个要返回的值快照,这个值实际上是上一次迭代就保存好的,下一次直接返回
     * 为什么这样做呢,这是为了和hasNext方法统一,因为hasNext方法判断的是下一个迭代的结点是或否为null
     * 如果我们返回结点的item,那么item可能因为结点被删除为null,那么就返回null了
     * 这不符合程序思维:因为hasNext表示有数据,但是next方法却返回null,并且LinkedBlockingDeque的“对外”规定item是不能为null的
     * 因此这里的值也是保存在获取下一个要返回结点时结点值的快照,这就是导致弱一致性的原因
     */
    E nextItem;

    /**
     * 最后一次迭代的结点,用于辅助remove方法;初始化时和删除结点后都被置为null
     */
    private Node<E> lastRet;

    /**
     * @return 返回头结点
     */
    abstract Node<E> firstNode();

    /**
     * @return 返回结点的后继
     */
    abstract Node<E> nextNode(Node<E> n);

    /**
     * 迭代器的构造器
     * 会初始化下一次要使用的数据
     */
    AbstractItr() {
        // set to initial position
        final ReentrantLock lock = LinkedBlockingDeque.this.lock;
        //获取锁
        lock.lock();
        try {
            //获取头结点赋值给next属性
            next = firstNode();
            //为nextItem属性赋值,如果next为null,那么该属性也为null,否则该属性值为next结点的item值
            nextItem = (next == null) ? null : next.item;
        } finally {
            //释放锁
            lock.unlock();
        }
    }


    /**
     * 获取下一个要被迭代的结点
     *
     * @param n 当前结点
     * @return 下一个要被迭代的结点
     */
    private Node<E> succ(Node<E> n) {
        /*开启一个死循环查找,因为n可能作为中间结点被移除了,或者头结点被移除了,需要排除这些因素的影响*/
        for (; ; ) {
            //获取n的后继结点s
            Node<E> s = nextNode(n);
            /*如果s等于null,说明到了队列尾部,直接返回null,可以看到在迭代器中以某个结点的next为null来判断队尾*/
            if (s == null)
                return null;
                /*否则,如果s的item不等于null,表示s没有被移除,那么就返回s*/
            else if (s.item != null)
                return s;
                /*
                 * 确认是否作为头结点被删除
                 * 否则,如果s等于n,说明n结点作为first结点被移除了队列,那么查找最新的first结点并返回,从头开始迭代
                 * 这里我们就能明白在头结点出队列的时候将next指向自己的作用
                 */
            else if (s == n)
                return firstNode();
                /*
                 * 确认是否作为中间结点被删除
                 * 否则,表示s结点作为被删除的中间结点,在remove(o)中我们就说过
                 * 中间被删除的结点的item为null,并且它的前驱后继直接关联,但是它自己的前驱后继关系并没有移除
                 * 因此需要跳过被删除的结点,继续向后查找
                 */
            else
                n = s;
        }
    }

    /**
     * 移动到下一个迭代的结点并且设置相关参数的方法,在next方法中被调用
     */
    void advance() {
        final ReentrantLock lock = LinkedBlockingDeque.this.lock;
        //获取锁
        lock.lock();
        try {
            // assert next != null;
            //获取下一个要被迭代的结点赋给next属性
            next = succ(next);
            //为nextItem属性赋值,如果next为null,那么该属性也为null,否则该属性值为next结点的item值
            nextItem = (next == null) ? null : next.item;
        } finally {
            //释放锁
            lock.unlock();
        }
    }

    /**
     * @return 是否还有可迭代的数据,true 是 false 否
     */
    public boolean hasNext() {
        //判断nextNode是否不为null就行了
        return next != null;
    }

    /**
     * 返回要迭代的数据,并且获取下一次要迭代的数据
     */
    public E next() {
        //如果next为null,表示要迭代的数据不存在,抛出NoSuchElementException异常
        if (next == null)
            throw new NoSuchElementException();
        //lastRet等于此时的next,即最后一次迭代的元素
        lastRet = next;
        //获取nextItem
        E x = nextItem;
        //移动到下一个迭代的结点并且设置相关参数的方法
        //可以看到,下一次要迭代和返回的数据在上一次第二代中就保存好了,因此说是弱一致性的。
        advance();
        //返回快照x
        return x;
    }

    /**
     * 移除上一次next方法返回的元素,即最后一次迭代的结点
     */
    public void remove() {
        //如果lastRet为null,表示最后迭代的结点已经被移除了,或者还没有开始迭代,直接抛出IllegalStateException异常
        Node<E> n = lastRet;
        if (n == null)
            throw new IllegalStateException();
        //lastRet重置为null,表示最后迭代的结点已经被移除了,这里可以看出如果连续调用remove方法肯定会抛出异常
        lastRet = null;
        final ReentrantLock lock = LinkedBlockingDeque.this.lock;
        //获取锁
        lock.lock();
        try {
            //如果n结点的item不为null,即还没有被其他线程移除,那么通过迭代器的remove方法移除
            if (n.item != null)
                //调用unlink方法移除指定结点,该方法在remove(o)部分已经讲过了
                unlink(n);
        } finally {
            //解锁
            lock.unlock();
        }
    }
}

/**
 * 位于LinkedBlockingDeque中的Iterator迭代器的内部类实现
 */
private class Itr extends AbstractItr {
    /**
     * @return 返回头结点
     */
    Node<E> firstNode() {
        return first;
    }

    /**
     * @param n 指定结点
     * @return 返回指定结点的后继
     */
    Node<E> nextNode(Node<E> n) {
        return n.next;
    }
}

3 LinkedBlockingDeque的总结

LinkedBlockingDeque可以看作LinkedList集合的线程安全的实现,可以在队头和队尾对元素做出队和入队操作,而LinkedBlockingQueue只能在队尾入队列,在队头出队列。LinkedBlockingDeque相比于LinkedBlockingQueue,可操作的方法和方式更加多样。

但是我们也看到LinkedBlockingDeque内部只有一个锁,出队、入队、size、迭代等操作都需要获取该锁。而LinkedBlockingQueue则有两把锁,分别对队尾的生产者线程和队头的消费者线程应用不同的锁,因此LinkedBlockingQueue的并发度比LinkedBlockingDeque更高,带来的问题是迭代等需要遍历整个队列的操作需要同时获取两把锁。

相关文章:

  1. LinkedBlockingQueue:JUC—LinkedBlockingQueue源码深度解析
  2. LinkedList:Java集合—LinkedList的源码深度解析以及应用介绍

如果有什么不懂或者需要交流,可以留言。另外希望点赞、收藏、关注,我将不间断更新各种Java学习博客!