您好,欢迎来到二三四教育网。
搜索
您的当前位置:首页Java1.8-LinkedBlockingDeque源码学习(

Java1.8-LinkedBlockingDeque源码学习(

来源:二三四教育网

一、概述

  LinkedBlockingDeque是一个基于链表实现的有界的阻塞双端队列,该类和LinkedBlockingQueue的很像,只不过这个类是双端队列,队尾也可以出队,队头也可以入队,同样该队列的容量是可选的,并且元素不能为null。

二、源码

1. 继承结构
public class LinkedBlockingDeque<E>
    extends AbstractQueue<E>
    implements BlockingDeque<E>, java.io.Serializable {

LinkedBlockingDeque同样继承自AbstractQueue,并且实现了双端阻塞队列BlockingDeque接口,BlockingDeque前面说过,该接口扩展自BlockingQueue和Deque,另外该类支持序列化。

2. 属性

接下来,我们来看一下他的属性:

/**
 * 对头节点
 * 队头节点固定特性: (first == null && last == null) ||
 *            (first.prev == null && first.item != null)
 */
transient Node<E> first;

/**
 * 队尾节点
 * 队尾节点固定特性: (first == null && last == null) ||
 *            (last.next == null && last.item != null)
 */
transient Node<E> last;

/** 队列中元素数量 */
private transient int count;

/** 队列的最大容量 */
private final int capacity;

/** 队列的主访问锁 */
final ReentrantLock lock = new ReentrantLock();

/** 队列的出队Condition */
private final Condition notEmpty = lock.newCondition();

/** 队列的入队Condition */
private final Condition notFull = lock.newCondition();

可以看到,和LinkedBlockingQueue不同的是,LinkedBlockingDeque只提供了一个访问锁。

3. 构造方法
public LinkedBlockingDeque() {
    this(Integer.MAX_VALUE);
}

public LinkedBlockingDeque(int capacity) {
    if (capacity <= 0) throw new IllegalArgumentException();
    // 初始化容量
    this.capacity = capacity;
}

public LinkedBlockingDeque(Collection<? extends E> c) {
    this(Integer.MAX_VALUE);
    final ReentrantLock lock = this.lock;
    lock.lock(); // Never contended, but necessary for visibility
    try {
        for (E e : c) {
            元素非空
            if (e == null)
                throw new NullPointerException();
            // 调用了linkLast方法
            if (!linkLast(new Node<E>(e)))
                throw new IllegalStateException("Deque full");
        }
    } finally {
        lock.unlock();
    }
}

三个构造方法都很简单,第三个构造方法中调用了linkLast方法,将集合元素添加到队列尾部,这个方法后面再来说下。

4. 内部类

和LinkedBlockingQueue类似,LinkedBlockingDeque中也提供了一个内部类Node,用来保存元素。因为是双端队列,所以Node中除了保存元素外,还提供了指向前驱节点和后驱节点的指针,来简单看一下源码:

/** Doubly-linked list node class */
static final class Node<E> {
    /**
     * The item, or null if this node has been removed.
     */
    E item;

    /**
     * One of:
     * - the real predecessor Node
     * - this Node, meaning the predecessor is tail
     * - null, meaning there is no predecessor
     */
    Node<E> prev;

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

    Node(E x) {
        item = x;
    }
}
5. 方法
5.1 add方法

默认的 add(E) 方法,内部调用了addLast方法,表示从队尾插入,插入成功返回true,如果队列已满抛出异常:

public boolean add(E e) {
    addLast(e);
    return true;
}

然后我们来看下 addLast 方法,该方法又调用了 offerLast 方法:

public void addLast(E e) {
    if (!offerLast(e))
        throw new IllegalStateException("Deque full");
}

我们再来看下 offerLast 方法,该方法在队列满的时候不会抛出异常,而是返回false:

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();
    }
}

offerLast 方法进行了加锁操作,并且调用了 linkLast 方法方法来实现,我们再来看下 linkLast 方法:

private boolean linkLast(Node<E> node) {
    // assert lock.isHeldByCurrentThread();
    // 如果队列中元素数量超过容量限制,返回false
    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上等待的线程
    notEmpty.signal();
    return true;
}

可以看到,这几个方法最终都是通过 linkLast 来进行实现的。而addFirst方法同样是调用offerFirst方法,而offerFirst又是通过调用linkFirst方法来实现的,我们来看下这个 linkFirst方法:

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;
}

这里也没什么好说的,就是将元素添加到队头位置,然后添加的时候判断下队尾节点是否为空(其实也就是队列是否为空),然后进行后续的操作。

5.2 offer方法

首先,默认的 offer(E) 方法内部调用了 offerLast 方法,而 offerLast 以及 offerFirst 方法的调用我们前面已经说过了,这里就不多说了:

public boolean offer(E e) {
    return offerLast(e);
}

接下来,来看下超时的 offer(E,long,TimeUnit) 方法:

public boolean offer(E e, long timeout, TimeUnit unit)
    throws InterruptedException {
    return offerLast(e, timeout, unit);
}

该方法又调用了超时的 offerLast(E,long,TimeUnit) 方法:

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();
    }
}

这里添加的时候会进行循环判断,如果队列已满,并且已经超时,直接返回;如果没有超时,则等待对应的时间。而 offerFirst(E,long,TimeUnit) 逻辑和这个是相似的,只不过内部调用的是 linkFirst而已,就不多说了。

5.3 put方法

put方法内部是直接调用 putLast 来实现的,我们来简单看下 putLast的实现:

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 {
        // 调用linkLast方法
        while (!linkLast(node))
            // 如果队列已满,阻塞
            notFull.await();
    } finally {
        lock.unlock();
    }
}

方法比较简单,内部调用 linkLast 方法,然后添加时判断是否队列已满,如果已满,notFull条件上的线程一直阻塞。而 putFirst 的方法实现相似,不多说了。

5.4 poll方法

poll 方法内部调用的是 pollFirst 方法,我们来简单看下:

public E pollFirst() {
    final ReentrantLock lock = this.lock;
    lock.lock();
    try {
        return unlinkFirst();
    } finally {
        lock.unlock();
    }
}

pollFirst 方法则是调用 unlinkFirst 来实现移除元素功能,我们再来看下这个方法:

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;
}

这个方法也比较简单,就是移除第一个节点,然后更新下头节点,中间会有一些简单的判断逻辑。当然还有一个对应的 unlinkLast 方法,操作相似,不多说了。

另外,这里顺便说下 remove 方法。这个方法比较简单,内部会调用 removeFirst 方法,而该方法内部又回调用 pollFirst 方法来实现,同样还有对应的 removeLast 则是调用 pollLast 方法。

5.5 take方法

take 方法也是相同的道理,内部调用的是 takeFirst 方法,我们来看下这个方法:

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();
    }
}

这里,会通过 unlinkFirst 方法获取元素,获取元素的时候如果队列为空,notEmpty上的线程会进行阻塞。

5.6 peek方法

peek 方法也是同样的道理,内调调用 peekFirst,该方法也比较简单:

public E peekFirst() {
    final ReentrantLock lock = this.lock;
    lock.lock();
    try {
        // 如果队列不为空,获取队列的头节点元素返回
        return (first == null) ? null : first.item;
    } finally {
        lock.unlock();
    }
}

另外,getFirst 方法是调用的 peekFirst,而 element 是调用的 getFirst 方法,所以这几个方法内部实现都是相同的。

其实该类的方法大部分都是通用的,都比较简单,这里就不多说了。如果用到哪个方法,到时候直接再看源码也可以。

三、总结

由于LinkedBlockingDeque比较简单,所以上面简单介绍下源码后,这里来简单总结下:

  • LinkedBlockingDeque是一个基于链表实现的有界的双端阻塞队列;
  • 该队列出队和入队都是使用的同一个锁;
  • 该队列和其他常规阻塞队列一样,不能存储null值。

本文如未解决您的问题请添加抖音号:51dongshi(抖音搜索懂视),直接咨询即可。

热门图文

Copyright © 2019-2025 how234.cn 版权所有 赣ICP备2023008801号-2

违法及侵权请联系:TEL:199 1889 7713 E-MAIL:2724546146@qq.com

本站由北京市万商天勤律师事务所王兴未律师提供法律服务