LinkedBlockingDeque源码,原理和常用方法,使用场景介绍

概览和简介

LinkedBlockingDeque 是juc 包提供的一个基于链表的双向可选有界的阻塞队列。

通过把参数传给构造参数创建有界的队列可以防止过度的膨胀。没特别指定容量的话,默认最大的容量是 Integer#MAX_VALUE =2^31-1。

链表的每个节点insert时动态创建的。

如果没有阻塞,大多操作能在常数时间完成。除了 #remove(Object) ,removeFirstOccurrence,removeLastOccurrence,contains,iterator 方法和批量操作,这些操作时间复杂度是O(n)。

阻塞队列的特点是,如果队列为空,take/pop一个元素会一直等待(remove 会报错,pull会返回null,poll 看情况,get 会抛异常);如果队列已满,offer/put 一个元素也会阻塞一直等待(add 会错报容量已满)。

数据结构

数据结构本身不是很复杂,首先有一个内部类 final class Node<E> ,用于包装每个节点的数据。另外有成员属性 Node<E> first,Node<E> last,当前拥有元素数量int count ,容量 int capacity;

这下都是常见的,还有用于实现阻塞的 ReentrantLock lock 对应的两个Condition,notEmpty和notFull

Node<E>

final 的内部类,只有三个成员变量

E item 当前节点数据
Node<E> prev  指向当前节点前驱节点的地址
Node<E> next  指向当前节点下个节点的地址

常用方法

无参构造方法

无参构造方法返回的对象默认队列长度是Integer的最大值。2^32-1

构造方法 LinkedBlockingDeque(Collection<? extends E> c)

   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();
                if (!linkLast(new Node<E>(e)))
                    throw new IllegalStateException("Deque full");
            }
        } finally {
            lock.unlock();
        }
    }

容量是Integer 最大值

操作前lock.lock()

try {} finally {} 形式释放锁,

取每一个元素,依次放置的链表最后面

添加一个元素  void addFirst(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();
        }

关键的 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;
    }

主要逻辑是,先判断当前容量是不是超了,然后把当前节点的的next 指向原来的first,把原first的prev指向当前节点,同时注意如果是第一个元素进来,初始化一下 last 指针。

常规链表操作完,要体现一下阻塞队列的特征,如果有线程在进行类似 take的操作,可能会阻塞等待,所以要用 Condition notEmpty 进行通知,让其可以被唤醒,进行对应操作。

boolean offerFirst(E e)/boolean offerLast(E e)

这个和add方法的区别是,容量满了没加进去,不会直接报错,只会返回 false 

putFirst(E e)/putLast(E e)

这两个方法在队列已满的情况下会阻塞,这就是上面方法不同的地方。

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

一个关键的不同就在于,如果尝试linkFirst 失败了,会进行await,等待拿掉一个元素 signal (notify的意思)。

另外有一个点,如果多个线程都在这里await 了,取出一个元素只会signal某一个线程。

boolean offerFirst/offerLast(E e, long timeout, TimeUnit unit)

在队列最前面或者最后面插入一个元素,会等待指定的时间,指定时间后还没成功,就会返回false。

取出一个元素 E removeFirst()/removeLast()

    public E removeFirst() {
        E x = pollFirst();
        if (x == null) throw new NoSuchElementException();
        return x;
    }

关键方法E 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;
    }

取出其中的第一个,然后标志这个队列肯定已经不是满的,notFull.signal(); 想要 put/offer 可以继续进行了。

E pollFirst()/pollLast()

和remove 方法差不多的,只不多不会抛空指针,会直接返回null

E pollFirst(long timeout, TimeUnit unit)/pollLast(long timeout, TimeUnit unit)

这个重载的方法设计的很奇怪,如果给了超时时间,反而会等待指定的时间,如果到了时间还没取到才会返回null.

E takeFirst()/takeLast()

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

如果没取到会进行一直等待。

E getFirst()/getLast() 

这个没什么,取到的为null就抛异常

E peekFirst()/peekLast()

这个对队列本身没有改变,知识对首尾的一次查看操作

    public E peekFirst() {
        final ReentrantLock lock = this.lock;
        lock.lock();
        try {
            return (first == null) ? null : first.item;
        } finally {
            lock.unlock();
        }
    }

其他方法就不一一展开了,都比较普通。

应用场景和拓展点

JDK一个提供了7个阻塞队列

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

阻塞队列是典型的生产者消费者模式,和中间件MQ很类似,一边生产一边消费。这样做可以把生产者和消费者进行解耦。

在使用的时候,Node包装的对象,基本上是一个实现Runnable的对象,比如,创建线程池的时候有一个参数就是阻塞队列,用于保存提交过来的任务,然后线程池中的工作线程会不停的从阻塞队列中获取任务进行处理。

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值