深入分析LinkedBlockingQueue

来源:互联网 发布:nginx添加站点 编辑:程序博客网 时间:2024/06/08 04:54

LinkedBlockingQueue是可设置大小的阻塞队列,其内部由链表实现。当为其构造函数传入一个capacity值时,队列的大小就为capacity;否则队列的大小为Integer.MAX_VALUE。
这里写图片描述
图1 LinkedBlockingQueu的outline

应用场景
LinkedBlockingQueue内部采用链接实现,容量最大可达到Integer.MAX_VALUE。在服务器开发过程中,我们可以将客户端的消息添加到队列中进行异步处理。

源码分析
LinkedBlockingQueue中的链表队列由以下几个内部变量维护:
1、Node节点

    static class Node<E> {        E item;        Node<E> next;        Node(E x) { item = x; }    }

2、capacity,记录队列允许的大小

    private final int capacity;

3、count,记录当前队列中的元素个数

    private final AtomicInteger count = new AtomicInteger(0);

4、head、last,头节点和尾节点

    /** Head of linked list */    private transient Node<E> head;    /** Tail of linked list */    private transient Node<E> last;

5、获取锁及其等待条件

    /** Lock held by take, poll, etc */    private final ReentrantLock takeLock = new ReentrantLock();    /** Wait queue for waiting takes */    private final Condition notEmpty = takeLock.newCondition();

6、添加锁及其等待条件

    /** Lock held by put, offer, etc */    private final ReentrantLock putLock = new ReentrantLock();    /** Wait queue for waiting puts */    private final Condition notFull = putLock.newCondition();

在ArrayBlockingQueue的实现中,添加和获取元素采用一个ReentrantLock、两个Condition实现,而LinkedBlockingQueue则采用两把锁实现,这样做意味着在添加元素的同时可以获取元素。

构造函数:

    public LinkedBlockingQueue() {        this(Integer.MAX_VALUE);    }    public LinkedBlockingQueue(int capacity) {        if (capacity <= 0) throw new IllegalArgumentException();        this.capacity = capacity;        last = head = new Node<E>(null);    }    public LinkedBlockingQueue(Collection<? extends E> c) {        this(Integer.MAX_VALUE);        final ReentrantLock putLock = this.putLock;        putLock.lock(); // Never contended, but necessary for visibility        try {            int n = 0;            for (E e : c) {                if (e == null)                    throw new NullPointerException();                if (n == capacity)                    throw new IllegalStateException("Queue full");                enqueue(e);                ++n;            }            count.set(n);        } finally {            putLock.unlock();        }    }

在LinkedBlockingQueue(Collection

   public void put(E e) throws InterruptedException {        if (e == null) throw new NullPointerException();        int c = -1;        final ReentrantLock putLock = this.putLock;        final AtomicInteger count = this.count;        putLock.lockInterruptibly();        try {            // 队列已满,阻塞当前线程            while (count.get() == capacity) {                     notFull.await();            }            // 添加到队列中            enqueue(e);            c = count.getAndIncrement();            // 取操作允许同时进行            // 此处可找机会唤醒其他等待添加元素的线程            if (c + 1 < capacity)                notFull.signal();        } finally {            putLock.unlock();        }        // 如果c为0,那么就有可能存在阻塞的获取操作的线程        // 此处唤醒可能存在的获取操作的线程        if (c == 0)            signalNotEmpty();    }

相比ArrayBlockingQueue,LinkedBlockingQueue的put()方法增加了更多的额外操作。由于LinkedBlockingQueue中的获取和添加由不同的锁控制,因此在队列不为空且不满的情况下,读取操作可以同时进行。此时在添加完一个元素后,可以判断count的大小,以找机会唤醒其他等待添加或获取元素的线程。

take()方法:

    public E take() throws InterruptedException {        E x;        int c = -1;        final AtomicInteger count = this.count;        final ReentrantLock takeLock = this.takeLock;        takeLock.lockInterruptibly();        try {            // 队列为空,则阻塞当前线程                while (count.get() == 0) {                notEmpty.await();            }            // 获得元素            x = dequeue();            c = count.getAndDecrement();            // 添加操作允许同时进行            // 此处可找机会唤醒其他等待获取元素的线程            if (c > 1)                notEmpty.signal();        } finally {            takeLock.unlock();        }        // 如果c为capacity,那么就有可能存在阻塞的添加操作的线程        // 此处唤醒可能存在的添加操作的线程        if (c == capacity)            signalNotFull();        return x;    }

take()方法的逻辑和put()方法相似,只是判断条件正好相反。因此不在具体分析。

ArrayBlockingQueue和LinkedBlockingQueue的比较
相同点:
在功能使用上,两个阻塞队列有相似点,都可以设定大小,put()和take()方法都能完成阻塞功能。
不同点:
1、通过分析其源代码可以看到,ArrayBlockingQueue内部采用数组实现,LinkedBlockingQueue内部采用链表实现,因此LinkedBlockingQueue的remove()操作会比ArrayBlockingQueue更快。
2、LinkedBlockingQueue内部的读和取操作采用不同的锁控制,因此可以同时进行。而ArrayBlockingQueue内的读和取操作采用同一把锁控制,在同一个时刻只能进行读操作或取操作。
综上可以看到,在通常情况下,LinkedBlockingQueue的性能会比ArrayBlockingQueue更加好,而且ArrayBlockingQueue有的功能LinkedBlockingQueue都有。因此在实际使用过程中,LinkedBlockingQueue通常都是一个更好的选择。

0 0