Java并发编程:阻塞队列LinkedBlockingQueue

Java并发编程:阻塞队列(一)ArrayBlockingQueue

我们都知道ArrayList和LinkedLsit的区别,其实LinkedBlockingQueue和ArrayBlockingQueue之间也存在着类似的区别。因为它们都显现接口BlockingQueue并继承自Queue接口,所以LinkedBlockingQueue和ArrayBlockingQueue的API几乎是一样的,但它们的内部实现原理不太相同,当然使用LinkedBlockingQueue同样也能实现生产者消费者模式。

注:jdk1.7

目录

一、构造器

二、内部成员变量

三、方法显示原理

1、添加方法

2、移除方法


一、构造器

同样LinkedBlockingQueue的构造器有三个重载版本的构造器有三个重载版本:

public LinkedBlockingDeque() {
        this(Integer.MAX_VALUE);
}

public LinkedBlockingDeque(int capacity) {
        if (capacity <= 0) throw new IllegalArgumentException();
        this.capacity = capacity;
}

//创建大小默认值为Integer.MAX_VALUE的阻塞队列并添加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();
        }
}

LinkedBlockingQueue是一个由链表实现的有界队列阻塞队列,但大小默认值为Integer.MAX_VALUE,所以我们在使用LinkedBlockingQueue时建议手动传值,为其提供我们所需的大小,避免队列过大造成机器负载或者内存爆满等情况。

扫描二维码关注公众号,回复: 5000673 查看本文章

二、内部成员变量

LinkedBlockingQueue是一个基于链表的阻塞队列,其内部维持一个基于链表的数据队列。

public class LinkedBlockingQueue<E> extends AbstractQueue<E>
                    implements BlockingQueue<E>, java.io.Serializable {

    //节点类,用于存储数据
    static class Node<E> {
        E item;
        Node<E> next;
        Node<E> prev;
        Node(E x) { item = x; }
    }
    // 阻塞队列的大小,默认为Integer.MAX_VALUE 
    private final int capacity;

    // 当前阻塞队列中的元素个数 
    private transient int count;

    // 阻塞队列的头结点
    transient Node<E> first;

    // 阻塞队列的尾节点
    transient Node<E> last;

    //获取并移除元素时或增加使用的锁 
    final ReentrantLock lock = new ReentrantLock();

    // notEmpty条件对象,当队列没有数据时用于挂起执行删除的线程 
    private final Condition notEmpty = lock.newCondition();

    // notFull条件对象,当队列数据已满时用于挂起执行添加的线程 
    private final Condition notFull = lock.newCondition();

}

动态链表嘛,每个添加到LinkedBlockingQueue队列中的数据都将被封装成Node节点,节点中包含下一个next元素和上一个prev元素的信息,其中first和last分别指向队列的头结点和尾结点。

与ArrayBlockingQueue相同,LinkedBlockingQueue中有一把ReentrantLock锁;并且使用了不同的Condition条件对象作为等待队列,用于挂起take线程和put线程。

三、方法显示原理

1、添加方法

对于添加方法,主要指的是add,offer以及put

1)add

我们先来看一下add方法

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

因为LinkedBlockingQueue底层是一个双向队列,所以在LinkedBlockingQueue中,把add方法细分为了addFirst和addLast分别向队头和队尾插入数据。add方法默认调用的是addLast方法,即默认向队尾添加元素。

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

该方法中调用了offerLast方法,如果返回为true时,会抛出IllegalStateException,即队列已满

    public boolean offerLast(E e) {
        //添加元素为null直接抛出异常
        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方法

    private boolean linkLast(Node<E> node) {
        //再次判断队列是否已满,考虑并发情况
        if (count >= capacity)
            return false;
        //获取队尾元素
        Node<E> l = last;
        //将插入元素的前一个元素指向原队尾元素
        node.prev = l;
        //插入元素后成为队尾元素
        last = node;
        //如果队列为空,插入1个元素后,队尾队头指向该元素
        if (first == null)
            first = node;
        else
        //队列非空,原队尾元素的下一个元素指向该插入元素
            l.next = node;
        ++count;
        //唤醒调用移除方法的线程,执行元素获取操作
        notEmpty.signal();
        return true;
    }

这逻辑其实就和LinkedArray的add方法类似,不过是添加完后会唤醒获取并删除元素的线程

2)offer

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

调用的同是offerLast方法,与addLast不同的是offer方法在队列满时并不会抛出异常,而添加元素的这个操作会进入notEmpty中等待直至队列有空位置,这就需要使用put方法了

3)put

    public void put(E e) throws InterruptedException {
        putLast(e);
    }
    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();
        }
    }

可见在putLast中调用了linkLast方法循环判断队列是否满了,队满则当前线程将会被notFull条件对象挂起(调用await方法)加到等待队列中,直到队列有空档才会唤醒执行添加操作。

2、移除方法

移除的方法主要是指remove和poll以及take方法

remove同样分为removeFirst和removeLast

    public E remove() {
        return removeFirst();
    }

默认调用removeFirst从队头移除元素并获取该元素

    public E removeFirst() {
        E x = pollFirst();
        if (x == null) throw new NoSuchElementException();
        return x;
    }
    public E pollFirst() {
        final ReentrantLock lock = this.lock;
        lock.lock();
        try {
            return unlinkFirst();
        } finally {
            lock.unlock();
        }
    }
    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;
    }

poll方法

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

take方法

    public E take() throws InterruptedException {
        return 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();
        }
    }

可见,移除方法的逻辑和对应的添加方法十分类似,不过是队列中节点的两个"指针"移动规则不同,阻塞时由两个不同对象(notFull&notEmpty)来存储管理罢了。可以参照添加方法,就不在此赘述了~

看到这里,是不是发现LinkedBlockingQueue除了队列大小有所不同,数据存储容器不同(导致实现原理不同)与ArrayBlockingQueue在线程阻塞方面十分类似呢。不过是在不能满足添加或删除条件时放到两个Condition中,当满足了就分别取唤醒阻塞中的线程。这篇博客就当复习LinkedList和加深阻塞队列印象了:)

猜你喜欢

转载自blog.csdn.net/qq_39192827/article/details/86324457